From 4aa594a348231eb90ccb20bd7a140a949786bc7a Mon Sep 17 00:00:00 2001 From: tsteven4 <13596209+tsteven4@users.noreply.github.com> Date: Tue, 16 May 2023 15:44:47 -0600 Subject: [PATCH] refactor unicsv & xcsv date time handling (#1114) * refactor unicsv date time handling. * restrict utc option range to match Qt offsetfromutc * move xcsv from C-style legacy time to Qt. * fill in lower order date/time fields when parsing. * don't return date/time if we don't have one! * enhance xcsv date/time testing, fix bug. * csv format date time adjustements. use QDateTime::fromString to parse iso date times. return invalid QTime from addhms if parsing fails entirely. * datetime display fixes. For the xcsv writer: avoid priting date/time fields when there isn't a valid date/time. when printing with am/pm use times from a 12 hour clock. add support for printing dotnet time. use QDateTime::toString to print ISO time. For the xcsv and unicsv readers: use QDateTime::fromString instead of xml_parse_time when reading ISO datetimes. This avoids xml_parse_times intentional odd behavior of treating non-timezoned times as UTC. add a test of the xcsv writer time related fields. This only runs in America/Denver time zone. As setting the time zone is system dependent the test only runs if tzselect is available. If so it assumes America/Denver is available. correct documentation to give a sensible format for xcsv field GMT_TIME. * add reference files for new test. * silence xcsv reader conversion warnings on empty strings. * warn on parse errors reading excel time. * xcsv date/time fixes. fix addhms to account for 12/24 hour clock. don't print invalid datetimes with iso_time, iso_time_ms. add testcases to exercise all the xcsv reader date/time flavors. * clarify HMSL, HMSG wrt 12/24 hour clocks. * add missing reference file. * enhance xcsv for HMS[L|G] before or after [LOCAL|GMT]_TIME * restore YYYYYMMDD to use UTC. This has been broken for some time. Mail from 2012 indicates the intent was UTC (https://sourceforge.net/p/gpsbabel/mailman/message/29544538/) * unicsv review catches don't ripple into trouble with msec rounding. pass outputs that may or may not be written as references. * retire xcsv fields HMSG_TIME, HMSL_TIME. This is potentially a user visible change. It could require users to rewrite any style files they have created that use these fields. These are replaced by repeated use of GMT_TIME and LOCAL_TIME. This eliminates our pain over 12/24 hour clock issues. strptime/ strftime (as well as QTime) have distinct conversion specifiers for hours with 12/24 hour clocks. Our support for HMSG_TIME, HMSL_TIME used integer conversion specifiers for hours, minutes and seconds. This made it difficult to decide if a 12 or 24 hour clock should be used, and made it impossible to have reduced precision values with 12 hour clock using an AM/PM designation. We also always printed AM/PM designations. * fix whitespace in serialization reference file --- defs.h | 7 +- reference/datetime.xcsv | 7 + reference/datetime_read.xcsv | 7 + reference/datetime~xcsv.xcsv | 7 + reference/format3.txt | 30 ++- reference/help.txt | 11 + reference/localgmttime.csv | 3 + reference/localgmttime.xcsv | 2 + reference/realtime.csv | 2 +- reference/unicsv-local-utc.gpx | 35 +++ reference/unicsv-local-utc1.gpx | 35 +++ reference/unicsv-local.csv | 6 + reference/unicsv-local.gpx | 35 +++ reference/unicsv-local~csv.csv | 6 + reference/unicsv-local~gpx.csv | 6 + reference/unicsv-utc.csv | 6 + reference/unicsv-utc.gpx | 35 +++ style/garmin_g1000.style | 2 +- style/iblue747.style | 2 +- style/iblue757.style | 2 +- style/land_air_sea.style | 2 +- testo.d/track.test | 2 +- testo.d/unicsv_local.test | 43 ++++ testo.d/xcsv.test | 344 +++++++++++++++++++++++++- unicsv.cc | 246 ++++++++---------- unicsv.h | 16 +- util.cc | 102 ++++---- xcsv.cc | 260 ++++++++++--------- xcsv.h | 31 ++- xmldoc/chapters/styles.xml | 58 ++--- xmldoc/formats/options/unicsv-utc.xml | 7 +- 31 files changed, 973 insertions(+), 384 deletions(-) create mode 100644 reference/datetime.xcsv create mode 100644 reference/datetime_read.xcsv create mode 100644 reference/datetime~xcsv.xcsv create mode 100644 reference/localgmttime.csv create mode 100644 reference/localgmttime.xcsv create mode 100644 reference/unicsv-local-utc.gpx create mode 100644 reference/unicsv-local-utc1.gpx create mode 100644 reference/unicsv-local.csv create mode 100644 reference/unicsv-local.gpx create mode 100644 reference/unicsv-local~csv.csv create mode 100644 reference/unicsv-local~gpx.csv create mode 100644 reference/unicsv-utc.csv create mode 100644 reference/unicsv-utc.gpx create mode 100644 testo.d/unicsv_local.test diff --git a/defs.h b/defs.h index 3d217d559..b4b988993 100644 --- a/defs.h +++ b/defs.h @@ -27,6 +27,9 @@ #include // for optional #include // for move +#include // for QByteArray +#include // for QDate +#include // for QTime #include // for QDateTime #include // for QDebug #include // for QList, QList<>::const_iterator, QList<>::const_reverse_iterator, QList<>::count, QList<>::reverse_iterator @@ -1022,11 +1025,11 @@ inline int case_ignore_strncmp(const QString& s1, const QString& s2, int n) [[gnu::format(printf, 2, 0)]] int xvasprintf(char** strp, const char* fmt, va_list ap); char* strupper(char* src); char* strlower(char* src); -time_t mklocaltime(std::tm* time); -time_t mkgmtime(std::tm* time); +QDateTime make_datetime(QDate date, QTime time, bool is_localtime, bool force_utc, int utc_offset); bool gpsbabel_testmode(); gpsbabel::DateTime current_time(); QDateTime dotnet_time_to_qdatetime(long long dotnet); +long long qdatetime_to_dotnet_time(const QDateTime& dt); QString strip_html(const QString& utfstring); QString strip_nastyhtml(const QString& in); QString convert_human_date_format(const char* human_datef); /* "MM,YYYY,DD" -> "%m,%Y,%d" */ diff --git a/reference/datetime.xcsv b/reference/datetime.xcsv new file mode 100644 index 000000000..8c8513857 --- /dev/null +++ b/reference/datetime.xcsv @@ -0,0 +1,7 @@ +lat,lon,iso datetime +40.000000,-105.070000,1970-01-03T00:04:05-07:00 +40.050000,-105.120000,1970-01-03T05:04:05-07:00 +40.120000,-105.190000,1970-01-04T12:04:05-07:00 +40.170000,-105.000000,1970-01-04T17:04:05-07:00 +40.040000,-105.110000,2069-12-31T04:05:06-07:00 +40.900000,-105.900000, diff --git a/reference/datetime_read.xcsv b/reference/datetime_read.xcsv new file mode 100644 index 000000000..44c0e5ae7 --- /dev/null +++ b/reference/datetime_read.xcsv @@ -0,0 +1,7 @@ +LAT_DECIMAL,LON_DECIMAL,EXCEL_TIME,GMT_TIME-12HR,GMT_TIME-DATE-24HR,GMT_TIME-24HR,LOCAL_TIME-12HR,LOCAL_TIME-DATE-24HR,LOCAL_TIME-24HR,ISO_TIME,ISO_TIME_MS,NET_TIME,TIMET_TIME,TIMET_TIME_MS,YYYYMMDD_TIME +40.000000,-105.070000,1970-01-03T07:04:05Z +40.050000,-105.120000,1970-01-03T12:04:05Z +40.120000,-105.190000,1970-01-04T19:04:05Z +40.170000,-105.000000,1970-01-05T00:04:05Z +40.040000,-105.110000,2069-12-31T11:05:06Z +40.900000,-105.900000, diff --git a/reference/datetime~xcsv.xcsv b/reference/datetime~xcsv.xcsv new file mode 100644 index 000000000..8d4df0f1f --- /dev/null +++ b/reference/datetime~xcsv.xcsv @@ -0,0 +1,7 @@ +LAT_DECIMAL,LON_DECIMAL,EXCEL_TIME,GMT_TIME-12HR,GMT_TIME-DATE-24HR,GMT_TIME-24HR,LOCAL_TIME-12HR,LOCAL_TIME-DATE-24HR,LOCAL_TIME-24HR,ISO_TIME,ISO_TIME_MS,NET_TIME,TIMET_TIME,TIMET_TIME_MS,YYYYMMDD_TIME +40.000000,-105.070000,2.557129450231e+04,07:04:05 AM,1970-01-03 07:04:05,07:04:05,12:04:05 AM,1970-01-03 00:04:05,00:04:05,1970-01-03T07:04:05Z,1970-01-03T07:04:05Z,621357950450000000,198245,198245000,19700103 +40.050000,-105.120000,2.557150283565e+04,12:04:05 PM,1970-01-03 12:04:05,12:04:05,05:04:05 AM,1970-01-03 05:04:05,05:04:05,1970-01-03T12:04:05Z,1970-01-03T12:04:05Z,621358130450000000,216245,216245000,19700103 +40.120000,-105.190000,2.557279450231e+04,07:04:05 PM,1970-01-04 19:04:05,19:04:05,12:04:05 PM,1970-01-04 12:04:05,12:04:05,1970-01-04T19:04:05Z,1970-01-04T19:04:05Z,621359246450000000,327845,327845000,19700104 +40.170000,-105.000000,2.557300283565e+04,12:04:05 AM,1970-01-05 00:04:05,00:04:05,05:04:05 PM,1970-01-04 17:04:05,17:04:05,1970-01-05T00:04:05Z,1970-01-05T00:04:05Z,621359426450000000,345845,345845000,19700105 +40.040000,-105.110000,6.209346187500e+04,11:05:06 AM,2069-12-31 11:05:06,11:05:06,04:05:06 AM,2069-12-31 04:05:06,04:05:06,2069-12-31T11:05:06Z,2069-12-31T11:05:06Z,652913103060000000,3155713506,3155713506000,20691231 +40.900000,-105.900000,,,,,,,,,,,,, diff --git a/reference/format3.txt b/reference/format3.txt index cbfa55b61..8d9e11974 100644 --- a/reference/format3.txt +++ b/reference/format3.txt @@ -16,6 +16,8 @@ option xcsv prefer_shortnames Use shortname instead of description boolean ht option xcsv datum GPS datum (def. WGS 84) string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_xcsv.html#fmt_xcsv_o_datum +option xcsv utc Write timestamps with offset x to UTC time integer -14 +14 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_xcsv.html#fmt_xcsv_o_utc + internal rw---- tabsep All database fields on one tab-separated line xcsv https://www.gpsbabel.org/WEB_DOC_DIR/fmt_tabsep.html option tabsep snlen Max synthesized shortname length integer 1 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_tabsep.html#fmt_tabsep_o_snlen @@ -32,6 +34,8 @@ option tabsep prefer_shortnames Use shortname instead of description boolean option tabsep datum GPS datum (def. WGS 84) string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_tabsep.html#fmt_tabsep_o_datum +option tabsep utc Write timestamps with offset x to UTC time integer -14 +14 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_tabsep.html#fmt_tabsep_o_utc + file r-r--- v900 Columbus/Visiontac V900 files (.csv) v900 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_v900.html file rw---- csv Comma separated values xcsv @@ -50,6 +54,8 @@ option csv prefer_shortnames Use shortname instead of description boolean htt option csv datum GPS datum (def. WGS 84) string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_csv.html#fmt_csv_o_datum +option csv utc Write timestamps with offset x to UTC time integer -14 +14 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_csv.html#fmt_csv_o_utc + internal rw---- custom Custom "Everything" Style xcsv https://www.gpsbabel.org/WEB_DOC_DIR/fmt_custom.html option custom snlen Max synthesized shortname length integer 1 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_custom.html#fmt_custom_o_snlen @@ -66,6 +72,8 @@ option custom prefer_shortnames Use shortname instead of description boolean option custom datum GPS datum (def. WGS 84) string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_custom.html#fmt_custom_o_datum +option custom utc Write timestamps with offset x to UTC time integer -14 +14 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_custom.html#fmt_custom_o_utc + file --rw-- iblue747 csv Data Logger iBlue747 csv xcsv https://www.gpsbabel.org/WEB_DOC_DIR/fmt_iblue747.html option iblue747 snlen Max synthesized shortname length integer 1 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_iblue747.html#fmt_iblue747_o_snlen @@ -82,6 +90,8 @@ option iblue747 prefer_shortnames Use shortname instead of description boolean option iblue747 datum GPS datum (def. WGS 84) string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_iblue747.html#fmt_iblue747_o_datum +option iblue747 utc Write timestamps with offset x to UTC time integer -14 +14 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_iblue747.html#fmt_iblue747_o_utc + file --rw-- iblue757 csv Data Logger iBlue757 csv xcsv https://www.gpsbabel.org/WEB_DOC_DIR/fmt_iblue757.html option iblue757 snlen Max synthesized shortname length integer 1 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_iblue757.html#fmt_iblue757_o_snlen @@ -98,6 +108,8 @@ option iblue757 prefer_shortnames Use shortname instead of description boolean option iblue757 datum GPS datum (def. WGS 84) string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_iblue757.html#fmt_iblue757_o_datum +option iblue757 utc Write timestamps with offset x to UTC time integer -14 +14 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_iblue757.html#fmt_iblue757_o_utc + file rw---- exif jpg Embedded Exif-GPS data (.jpg) exif https://www.gpsbabel.org/WEB_DOC_DIR/fmt_exif.html option exif filename Set waypoint name to source filename boolean Y https://www.gpsbabel.org/WEB_DOC_DIR/fmt_exif.html#fmt_exif_o_filename @@ -142,6 +154,8 @@ option garmin301 prefer_shortnames Use shortname instead of description boolean option garmin301 datum GPS datum (def. WGS 84) string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_garmin301.html#fmt_garmin301_o_datum +option garmin301 utc Write timestamps with offset x to UTC time integer -14 +14 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_garmin301.html#fmt_garmin301_o_utc + file --rw-- garmin_g1000 csv Garmin G1000 datalog input filter file xcsv https://www.gpsbabel.org/WEB_DOC_DIR/fmt_garmin_g1000.html option garmin_g1000 snlen Max synthesized shortname length integer 1 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_garmin_g1000.html#fmt_garmin_g1000_o_snlen @@ -158,6 +172,8 @@ option garmin_g1000 prefer_shortnames Use shortname instead of description boole option garmin_g1000 datum GPS datum (def. WGS 84) string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_garmin_g1000.html#fmt_garmin_g1000_o_datum +option garmin_g1000 utc Write timestamps with offset x to UTC time integer -14 +14 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_garmin_g1000.html#fmt_garmin_g1000_o_utc + file rwrwrw gdb gdb Garmin MapSource - gdb gdb https://www.gpsbabel.org/WEB_DOC_DIR/fmt_gdb.html option gdb cat Default category on output (1..16) integer 1 16 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_gdb.html#fmt_gdb_o_cat @@ -206,6 +222,8 @@ option garmin_poi prefer_shortnames Use shortname instead of description boolean option garmin_poi datum GPS datum (def. WGS 84) string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_garmin_poi.html#fmt_garmin_poi_o_datum +option garmin_poi utc Write timestamps with offset x to UTC time integer -14 +14 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_garmin_poi.html#fmt_garmin_poi_o_utc + file rw---- garmin_gpi gpi Garmin Points of Interest (.gpi) garmin_gpi https://www.gpsbabel.org/WEB_DOC_DIR/fmt_garmin_gpi.html option garmin_gpi alerts Enable alerts on speed or proximity distance boolean https://www.gpsbabel.org/WEB_DOC_DIR/fmt_garmin_gpi.html#fmt_garmin_gpi_o_alerts @@ -358,6 +376,8 @@ option land_air_sea prefer_shortnames Use shortname instead of description boole option land_air_sea datum GPS datum (def. WGS 84) string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_land_air_sea.html#fmt_land_air_sea_o_datum +option land_air_sea utc Write timestamps with offset x to UTC time integer -14 +14 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_land_air_sea.html#fmt_land_air_sea_o_utc + file rwrwrw gtm gtm GPS TrackMaker gtm https://www.gpsbabel.org/WEB_DOC_DIR/fmt_gtm.html file rw---- arc txt GPSBabel arc filter file xcsv @@ -376,6 +396,8 @@ option arc prefer_shortnames Use shortname instead of description boolean htt option arc datum GPS datum (def. WGS 84) string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_arc.html#fmt_arc_o_datum +option arc utc Write timestamps with offset x to UTC time integer -14 +14 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_arc.html#fmt_arc_o_utc + file rw---- gpsdrive GpsDrive Format xcsv https://www.gpsbabel.org/WEB_DOC_DIR/fmt_gpsdrive.html option gpsdrive snlen Max synthesized shortname length integer 1 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_gpsdrive.html#fmt_gpsdrive_o_snlen @@ -392,6 +414,8 @@ option gpsdrive prefer_shortnames Use shortname instead of description boolean option gpsdrive datum GPS datum (def. WGS 84) string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_gpsdrive.html#fmt_gpsdrive_o_datum +option gpsdrive utc Write timestamps with offset x to UTC time integer -14 +14 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_gpsdrive.html#fmt_gpsdrive_o_utc + file rw---- gpsdrivetrack GpsDrive Format for Tracks xcsv https://www.gpsbabel.org/WEB_DOC_DIR/fmt_gpsdrivetrack.html option gpsdrivetrack snlen Max synthesized shortname length integer 1 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_gpsdrivetrack.html#fmt_gpsdrivetrack_o_snlen @@ -408,6 +432,8 @@ option gpsdrivetrack prefer_shortnames Use shortname instead of description bool option gpsdrivetrack datum GPS datum (def. WGS 84) string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_gpsdrivetrack.html#fmt_gpsdrivetrack_o_datum +option gpsdrivetrack utc Write timestamps with offset x to UTC time integer -14 +14 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_gpsdrivetrack.html#fmt_gpsdrivetrack_o_utc + file rwrwrw gpx gpx GPX XML gpx https://www.gpsbabel.org/WEB_DOC_DIR/fmt_gpx.html option gpx snlen Length of generated shortnames integer 32 1 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_gpx.html#fmt_gpx_o_snlen @@ -686,6 +712,8 @@ option openoffice prefer_shortnames Use shortname instead of description boolean option openoffice datum GPS datum (def. WGS 84) string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_openoffice.html#fmt_openoffice_o_datum +option openoffice utc Write timestamps with offset x to UTC time integer -14 +14 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_openoffice.html#fmt_openoffice_o_utc + file -w---- text txt Textual Output text https://www.gpsbabel.org/WEB_DOC_DIR/fmt_text.html option text nosep Suppress separator lines between waypoints boolean https://www.gpsbabel.org/WEB_DOC_DIR/fmt_text.html#fmt_text_o_nosep @@ -706,7 +734,7 @@ option unicsv datum GPS datum (def. WGS 84) string WGS 84 https://www.gpsbabel option unicsv grid Write position using this grid. string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_unicsv.html#fmt_unicsv_o_grid -option unicsv utc Write timestamps with offset x to UTC time integer -23 +23 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_unicsv.html#fmt_unicsv_o_utc +option unicsv utc Write timestamps with offset x to UTC time integer -14 +14 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_unicsv.html#fmt_unicsv_o_utc option unicsv format Write name(s) of format(s) from input session(s) boolean https://www.gpsbabel.org/WEB_DOC_DIR/fmt_unicsv.html#fmt_unicsv_o_format diff --git a/reference/help.txt b/reference/help.txt index abdfc5672..346a8a791 100644 --- a/reference/help.txt +++ b/reference/help.txt @@ -41,6 +41,7 @@ File Types (-i and -o options): urlbase Basename prepended to URL on output prefer_shortnames (0/1) Use shortname instead of description datum GPS datum (def. WGS 84) + utc Write timestamps with offset x to UTC time iblue747 Data Logger iBlue747 csv snlen Max synthesized shortname length snwhite (0/1) Allow whitespace synth. shortnames @@ -49,6 +50,7 @@ File Types (-i and -o options): urlbase Basename prepended to URL on output prefer_shortnames (0/1) Use shortname instead of description datum GPS datum (def. WGS 84) + utc Write timestamps with offset x to UTC time iblue757 Data Logger iBlue757 csv snlen Max synthesized shortname length snwhite (0/1) Allow whitespace synth. shortnames @@ -57,6 +59,7 @@ File Types (-i and -o options): urlbase Basename prepended to URL on output prefer_shortnames (0/1) Use shortname instead of description datum GPS datum (def. WGS 84) + utc Write timestamps with offset x to UTC time exif Embedded Exif-GPS data (.jpg) filename (0/1) Set waypoint name to source filename frame Time-frame (in seconds) @@ -79,6 +82,7 @@ File Types (-i and -o options): urlbase Basename prepended to URL on output prefer_shortnames (0/1) Use shortname instead of description datum GPS datum (def. WGS 84) + utc Write timestamps with offset x to UTC time garmin_g1000 Garmin G1000 datalog input filter file snlen Max synthesized shortname length snwhite (0/1) Allow whitespace synth. shortnames @@ -87,6 +91,7 @@ File Types (-i and -o options): urlbase Basename prepended to URL on output prefer_shortnames (0/1) Use shortname instead of description datum GPS datum (def. WGS 84) + utc Write timestamps with offset x to UTC time gdb Garmin MapSource - gdb cat Default category on output (1..16) bitscategory Bitmap of categories @@ -111,6 +116,7 @@ File Types (-i and -o options): urlbase Basename prepended to URL on output prefer_shortnames (0/1) Use shortname instead of description datum GPS datum (def. WGS 84) + utc Write timestamps with offset x to UTC time garmin_gpi Garmin Points of Interest (.gpi) alerts (0/1) Enable alerts on speed or proximity distance bitmap Use specified bitmap on output @@ -181,6 +187,7 @@ File Types (-i and -o options): urlbase Basename prepended to URL on output prefer_shortnames (0/1) Use shortname instead of description datum GPS datum (def. WGS 84) + utc Write timestamps with offset x to UTC time gtm GPS TrackMaker arc GPSBabel arc filter file snlen Max synthesized shortname length @@ -190,6 +197,7 @@ File Types (-i and -o options): urlbase Basename prepended to URL on output prefer_shortnames (0/1) Use shortname instead of description datum GPS datum (def. WGS 84) + utc Write timestamps with offset x to UTC time gpsdrive GpsDrive Format snlen Max synthesized shortname length snwhite (0/1) Allow whitespace synth. shortnames @@ -198,6 +206,7 @@ File Types (-i and -o options): urlbase Basename prepended to URL on output prefer_shortnames (0/1) Use shortname instead of description datum GPS datum (def. WGS 84) + utc Write timestamps with offset x to UTC time gpsdrivetrack GpsDrive Format for Tracks snlen Max synthesized shortname length snwhite (0/1) Allow whitespace synth. shortnames @@ -206,6 +215,7 @@ File Types (-i and -o options): urlbase Basename prepended to URL on output prefer_shortnames (0/1) Use shortname instead of description datum GPS datum (def. WGS 84) + utc Write timestamps with offset x to UTC time gpx GPX XML snlen Length of generated shortnames suppresswhite (0/1) No whitespace in generated shortnames @@ -341,6 +351,7 @@ File Types (-i and -o options): urlbase Basename prepended to URL on output prefer_shortnames (0/1) Use shortname instead of description datum GPS datum (def. WGS 84) + utc Write timestamps with offset x to UTC time text Textual Output nosep (0/1) Suppress separator lines between waypoints encrypt (0/1) Encrypt hints using ROT13 diff --git a/reference/localgmttime.csv b/reference/localgmttime.csv new file mode 100644 index 000000000..9f8e0e3f2 --- /dev/null +++ b/reference/localgmttime.csv @@ -0,0 +1,3 @@ +No,Latitude,Longitude,Name,Date,Time +1,40.000000,-105.000000,"WPT001",1970/01/03,03:04:05 +2,40.100000,-105.100000,"WPT002",2069/12/31,04:05:06 diff --git a/reference/localgmttime.xcsv b/reference/localgmttime.xcsv new file mode 100644 index 000000000..5ed0b9666 --- /dev/null +++ b/reference/localgmttime.xcsv @@ -0,0 +1,2 @@ +40.000000,-105.000000,1970-01-03,03:04:05,1970-01-03T03:04:05-07:00 +40.100000,-105.100000,2069-12-31,04:05:06,2069-12-31T04:05:06-07:00 diff --git a/reference/realtime.csv b/reference/realtime.csv index 48ee8caf3..7c83ad03a 100644 --- a/reference/realtime.csv +++ b/reference/realtime.csv @@ -1,4 +1,4 @@ --28.606309,41.491196,85.918,Wpt_RD,1970-01-01T00:00:00Z +-28.606309,41.491196,85.918,Wpt_RD, -28.605513,41.492136,0.027,Wpt_ahVv,1970-01-01T00:00:01.189Z -28.605013,41.492918,78.576,Wpt_ElFRt5,1970-01-01T00:00:02.317Z -28.604849,41.492946,,Wpt_Stg4W,1970-01-01T00:00:04.160Z diff --git a/reference/unicsv-local-utc.gpx b/reference/unicsv-local-utc.gpx new file mode 100644 index 000000000..76fbfb08e --- /dev/null +++ b/reference/unicsv-local-utc.gpx @@ -0,0 +1,35 @@ + + + + + + + WPT001 + WPT001 + WPT001 + + + + WPT001 + WPT001 + WPT001 + + + + WPT002 + WPT002 + WPT002 + + + + WPT002 + WPT002 + WPT002 + + + + WPT002 + WPT002 + WPT002 + + diff --git a/reference/unicsv-local-utc1.gpx b/reference/unicsv-local-utc1.gpx new file mode 100644 index 000000000..cd38eb9b2 --- /dev/null +++ b/reference/unicsv-local-utc1.gpx @@ -0,0 +1,35 @@ + + + + + + + WPT001 + WPT001 + WPT001 + + + + WPT001 + WPT001 + WPT001 + + + + WPT002 + WPT002 + WPT002 + + + + WPT002 + WPT002 + WPT002 + + + + WPT002 + WPT002 + WPT002 + + diff --git a/reference/unicsv-local.csv b/reference/unicsv-local.csv new file mode 100644 index 000000000..a91fe72ff --- /dev/null +++ b/reference/unicsv-local.csv @@ -0,0 +1,6 @@ +No,Latitude,Longitude,Name,Date,Time +1,36.000000,-87.000000,"WPT001",2022/01/10,23:00:00 +2,36.000000,-87.000000,"WPT001",2022/07/10,23:00:00 +3,36.000000,-87.000000,"WPT002",,23:00:00 +4,36.000000,-87.000000,"WPT002",2022/01/10, +5,36.000000,-87.000000,"WPT002",2022/07/10, diff --git a/reference/unicsv-local.gpx b/reference/unicsv-local.gpx new file mode 100644 index 000000000..fe131a10c --- /dev/null +++ b/reference/unicsv-local.gpx @@ -0,0 +1,35 @@ + + + + + + + WPT001 + WPT001 + WPT001 + + + + WPT001 + WPT001 + WPT001 + + + + WPT002 + WPT002 + WPT002 + + + + WPT002 + WPT002 + WPT002 + + + + WPT002 + WPT002 + WPT002 + + diff --git a/reference/unicsv-local~csv.csv b/reference/unicsv-local~csv.csv new file mode 100644 index 000000000..f75584783 --- /dev/null +++ b/reference/unicsv-local~csv.csv @@ -0,0 +1,6 @@ +No,Latitude,Longitude,Name,Date,Time +1,36.000000,-87.000000,"WPT001",2022/01/10,23:00:00 +2,36.000000,-87.000000,"WPT001",2022/07/10,23:00:00 +3,36.000000,-87.000000,"WPT002",,23:00:00 +4,36.000000,-87.000000,"WPT002",2022/01/10,00:00:00 +5,36.000000,-87.000000,"WPT002",2022/07/10,00:00:00 diff --git a/reference/unicsv-local~gpx.csv b/reference/unicsv-local~gpx.csv new file mode 100644 index 000000000..f75584783 --- /dev/null +++ b/reference/unicsv-local~gpx.csv @@ -0,0 +1,6 @@ +No,Latitude,Longitude,Name,Date,Time +1,36.000000,-87.000000,"WPT001",2022/01/10,23:00:00 +2,36.000000,-87.000000,"WPT001",2022/07/10,23:00:00 +3,36.000000,-87.000000,"WPT002",,23:00:00 +4,36.000000,-87.000000,"WPT002",2022/01/10,00:00:00 +5,36.000000,-87.000000,"WPT002",2022/07/10,00:00:00 diff --git a/reference/unicsv-utc.csv b/reference/unicsv-utc.csv new file mode 100644 index 000000000..5b027aff0 --- /dev/null +++ b/reference/unicsv-utc.csv @@ -0,0 +1,6 @@ +No,Latitude,Longitude,Name,utc_d,utc_t +1,36.000000,-87.000000,"WPT001",2022/01/10,23:00:00 +2,36.000000,-87.000000,"WPT001",2022/07/10,23:00:00 +3,36.000000,-87.000000,"WPT002",,23:00:00 +4,36.000000,-87.000000,"WPT002",2022/01/10, +5,36.000000,-87.000000,"WPT002",2022/07/10, diff --git a/reference/unicsv-utc.gpx b/reference/unicsv-utc.gpx new file mode 100644 index 000000000..76fbfb08e --- /dev/null +++ b/reference/unicsv-utc.gpx @@ -0,0 +1,35 @@ + + + + + + + WPT001 + WPT001 + WPT001 + + + + WPT001 + WPT001 + WPT001 + + + + WPT002 + WPT002 + WPT002 + + + + WPT002 + WPT002 + WPT002 + + + + WPT002 + WPT002 + WPT002 + + diff --git a/style/garmin_g1000.style b/style/garmin_g1000.style index 1855563cd..593a1162f 100644 --- a/style/garmin_g1000.style +++ b/style/garmin_g1000.style @@ -29,7 +29,7 @@ PROLOGUE Lcl Date, Lcl Time, UTCOfst, AtvWpt, Latitude, Longitude, Alt # INDIVIDUAL DATA FIELDS: # IFIELD GMT_TIME,"","%Y-%m-%d" #Lcl Date, -IFIELD HMSG_TIME,"","%d:%d:%d %s" #Lcl Time, +IFIELD GMT_TIME,"","%H:%M:%S" #Lcl Time, IFIELD IGNORE,"","%s" #UTCOfst, IFIELD IGNORE,"","%s" #AtvWpt, IFIELD LAT_DECIMAL, "", "%f" #Latitude, diff --git a/style/iblue747.style b/style/iblue747.style index 2b0b75978..838faa211 100644 --- a/style/iblue747.style +++ b/style/iblue747.style @@ -23,7 +23,7 @@ PROLOGUE INDEX,RCR,DATE,TIME,VALID,LATITUDE,N/S,LONGITUDE,E/W,HEIGHT,SPEED,HEADI IFIELD INDEX,"1","%d" # INDEX IFIELD CONSTANT,"T","%s" # RCR IFIELD GMT_TIME,"","%Y/%m/%d" # DATE -IFIELD HMSG_TIME,"","%02d:%02d:%02d" # TIME +IFIELD GMT_TIME,"","%H:%M:%S" # TIME IFIELD GPS_FIX,"","%s" # VALID # No fix, SPS, DGPS, PPS IFIELD LAT_DECIMAL,"","%f" # LATITUDE IFIELD LAT_DIR,"","%c" # N/S diff --git a/style/iblue757.style b/style/iblue757.style index 1e0395e66..db4dd274c 100644 --- a/style/iblue757.style +++ b/style/iblue757.style @@ -24,7 +24,7 @@ PROLOGUE INDEX,RCR,DATE,TIME,VALID,LATITUDE,N/S,LONGITUDE,E/W,HEIGHT,SPEED,HEADI IFIELD INDEX,"1","%d" # INDEX IFIELD CONSTANT,"T","%s" # RCR IFIELD GMT_TIME,"","%d/%m/%Y" # DATE -IFIELD HMSG_TIME,"","%02d:%02d:%02d" # TIME +IFIELD GMT_TIME,"","%H:%M:%S" # TIME IFIELD GPS_FIX,"","%s" # VALID # No fix, SPS, DGPS, PPS IFIELD LAT_DECIMAL,"","%f" # LATITUDE IFIELD LAT_DIR,"","%c" # N/S diff --git a/style/land_air_sea.style b/style/land_air_sea.style index 388b28bdf..d14034e5c 100644 --- a/style/land_air_sea.style +++ b/style/land_air_sea.style @@ -16,7 +16,7 @@ RECORD_DELIMITER NEWLINE # Individual data fields in order of appearance IFIELD LOCAL_TIME,"","%m-%d-%Y" -IFIELD HMSG_TIME,"","%d:%d:%d" +IFIELD LOCAL_TIME,"","%H:%M:%S" IFIELD LAT_HUMAN_READABLE,"","%c %d°%d'%f\"" IFIELD LON_HUMAN_READABLE,"","%c %d°%d'%f\"" IFIELD PATH_SPEED_MPH,"","%.1fmph" diff --git a/testo.d/track.test b/testo.d/track.test index adb9afbe1..299247e56 100644 --- a/testo.d/track.test +++ b/testo.d/track.test @@ -21,7 +21,7 @@ compare ${REFERENCE}/track/trackfilter3.gpx ${TMPDIR}/trackfilter3.gpx # Exercise the 'faketime' filter. The middle of the three points has # time so we can exercise the 'forced' option, too. -gpsbabel -t -i unicsv -f ${REFERENCE}/track/trackfilter_faketime.txt -x track,faketime=20100506060000+5 -o gpx -F ${TMPDIR}/ft.gpx +gpsbabel -t -i unicsv,utc -f ${REFERENCE}/track/trackfilter_faketime.txt -x track,faketime=20100506060000+5 -o gpx -F ${TMPDIR}/ft.gpx compare ${REFERENCE}/track/trackfilter_faketime.gpx ${TMPDIR}/ft.gpx gpsbabel -t -i unicsv -f ${REFERENCE}/track/trackfilter_faketime.txt -x track,faketime=f20100506060000+5 -o gpx -F ${TMPDIR}/ftf.gpx compare ${REFERENCE}/track/trackfilter_faketime_forced.gpx ${TMPDIR}/ftf.gpx diff --git a/testo.d/unicsv_local.test b/testo.d/unicsv_local.test new file mode 100644 index 000000000..8cc57aaa3 --- /dev/null +++ b/testo.d/unicsv_local.test @@ -0,0 +1,43 @@ +if command -v tzselect >/dev/null 2>&1 ; then + export TZ='America/Los_Angeles' + + echo " including local timezone tests" + # test interpretation of local date and time, + # with and without optional utc override, with and without offsets. + # these gpx files erroneously show the invented 1970/01/01 date. + gpsbabel -i unicsv -f ${REFERENCE}/unicsv-local.csv -o gpx -F ${TMPDIR}/unicsv-local.gpx + compare ${REFERENCE}/unicsv-local.gpx ${TMPDIR}/unicsv-local.gpx + gpsbabel -i unicsv,utc -f ${REFERENCE}/unicsv-local.csv -o gpx -F ${TMPDIR}/unicsv-local-utc.gpx + compare ${REFERENCE}/unicsv-local-utc.gpx ${TMPDIR}/unicsv-local-utc.gpx + gpsbabel -i unicsv,utc=1 -f ${REFERENCE}/unicsv-local.csv -o gpx -F ${TMPDIR}/unicsv-local-utc1.gpx + compare ${REFERENCE}/unicsv-local-utc1.gpx ${TMPDIR}/unicsv-local-utc1.gpx + + # test display of local date and time, + # with and without optional utc override, with and without offsets. + gpsbabel -i gpx -f ${REFERENCE}/unicsv-local.gpx -o unicsv -F ${TMPDIR}/unicsv-local~gpx.csv + compare ${REFERENCE}/unicsv-local~gpx.csv ${TMPDIR}/unicsv-local~gpx.csv + gpsbabel -i gpx -f ${REFERENCE}/unicsv-local-utc.gpx -o unicsv,utc -F ${TMPDIR}/unicsv-local-utc~gpx.csv + compare ${REFERENCE}/unicsv-local~gpx.csv ${TMPDIR}/unicsv-local-utc~gpx.csv + gpsbabel -i gpx -f ${REFERENCE}/unicsv-local-utc1.gpx -o unicsv,utc=1 -F ${TMPDIR}/unicsv-local-utc1~gpx.csv + compare ${REFERENCE}/unicsv-local~gpx.csv ${TMPDIR}/unicsv-local-utc1~gpx.csv + + # test echo of local date and time, + # with and without optional utc override, with and without offsets. + gpsbabel -i unicsv -f ${REFERENCE}/unicsv-local.csv -o unicsv -F ${TMPDIR}/unicsv-local~csv.csv + compare ${REFERENCE}/unicsv-local~csv.csv ${TMPDIR}/unicsv-local~csv.csv + gpsbabel -i unicsv,utc -f ${REFERENCE}/unicsv-local.csv -o unicsv,utc -F ${TMPDIR}/unicsv-local-utc~csv.csv + compare ${REFERENCE}/unicsv-local~csv.csv ${TMPDIR}/unicsv-local-utc~csv.csv + gpsbabel -i unicsv,utc=1 -f ${REFERENCE}/unicsv-local.csv -o unicsv,utc=1 -F ${TMPDIR}/unicsv-local-utc1~csv.csv + compare ${REFERENCE}/unicsv-local~csv.csv ${TMPDIR}/unicsv-local-utc1~csv.csv + + unset -v TZ +fi + +# make sure utc_d, utc_t ignore utc option - it only overrides local times. +gpsbabel -i unicsv -f ${REFERENCE}/unicsv-utc.csv -o gpx -F ${TMPDIR}/unicsv-utc.gpx +compare ${REFERENCE}/unicsv-utc.gpx ${TMPDIR}/unicsv-utc.gpx +gpsbabel -i unicsv,utc -f ${REFERENCE}/unicsv-utc.csv -o gpx -F ${TMPDIR}/unicsv-utc-utc.gpx +compare ${REFERENCE}/unicsv-utc.gpx ${TMPDIR}/unicsv-utc-utc.gpx +gpsbabel -i unicsv,utc=1 -f ${REFERENCE}/unicsv-utc.csv -o gpx -F ${TMPDIR}/unicsv-utc-utc1.gpx +compare ${REFERENCE}/unicsv-utc.gpx ${TMPDIR}/unicsv-utc-utc1.gpx + diff --git a/testo.d/xcsv.test b/testo.d/xcsv.test index a356f63eb..2a745ab8d 100644 --- a/testo.d/xcsv.test +++ b/testo.d/xcsv.test @@ -28,13 +28,13 @@ echo "IFIELD SHORTNAME, , %s" >> ${TMPDIR}/testo2.style echo "IFIELD ALT_METERS, -99999999.0, %.0f" >> ${TMPDIR}/testo2.style echo "IFIELD IGNORE, , %s" >> ${TMPDIR}/testo2.style echo "IFIELD GMT_TIME, , %Y/%m/%d" >> ${TMPDIR}/testo2.style -echo "IFIELD HMSG_TIME, , %d:%d:%d" >> ${TMPDIR}/testo2.style +echo "IFIELD GMT_TIME, , %H:%M:%S" >> ${TMPDIR}/testo2.style rm -f ${TMPDIR}/grid-utm~xscv.gpx gpsbabel -i xcsv,style=${TMPDIR}/testo2.style -f ${REFERENCE}/grid-utm.csv -o gpx -F ${TMPDIR}/grid-utm~xscv.gpx compare ${REFERENCE}/grid-utm~xscv.gpx ${TMPDIR}/grid-utm~xscv.gpx # test TRACK_NAME, TRACK_NEW -echo 'DESCRIPTION track style test 1' >>${TMPDIR}/track1.style +echo 'DESCRIPTION track style test 1' >${TMPDIR}/track1.style echo 'EXTENSION csv' >>${TMPDIR}/track1.style echo 'FIELD_DELIMITER COMMA' >>${TMPDIR}/track1.style echo 'RECORD_DELIMITER NEWLINE' >>${TMPDIR}/track1.style @@ -47,7 +47,7 @@ gpsbabel -i xcsv,style=${TMPDIR}/track1.style -f ${REFERENCE}/track/track1.csv - compare ${REFERENCE}/track/track1-2~csv.gpx ${TMPDIR}/track1~csv.gpx # flip TRACK_NAME, TRACK_NEW order -echo 'DESCRIPTION track style test 2' >>${TMPDIR}/track2.style +echo 'DESCRIPTION track style test 2' >${TMPDIR}/track2.style echo 'EXTENSION csv' >>${TMPDIR}/track2.style echo 'FIELD_DELIMITER COMMA' >>${TMPDIR}/track2.style echo 'RECORD_DELIMITER NEWLINE' >>${TMPDIR}/track2.style @@ -60,7 +60,7 @@ gpsbabel -i xcsv,style=${TMPDIR}/track2.style -f ${REFERENCE}/track/track2.csv - compare ${REFERENCE}/track/track1-2~csv.gpx ${TMPDIR}/track2~csv.gpx # ROUTE_NAME -echo 'DESCRIPTION route style test 1' >>${TMPDIR}/route1.style +echo 'DESCRIPTION route style test 1' >${TMPDIR}/route1.style echo 'EXTENSION csv' >>${TMPDIR}/route1.style echo 'FIELD_DELIMITER COMMA' >>${TMPDIR}/route1.style echo 'RECORD_DELIMITER NEWLINE' >>${TMPDIR}/route1.style @@ -72,7 +72,7 @@ gpsbabel -i xcsv,style=${TMPDIR}/route1.style -f ${REFERENCE}/route/route1.csv - compare ${REFERENCE}/route/route1~csv.gpx ${TMPDIR}/route1~csv.gpx # gmsd fields -echo 'DESCRIPTION gmsd test' >> ${TMPDIR}/gmsd.style +echo 'DESCRIPTION gmsd test' > ${TMPDIR}/gmsd.style echo 'EXTENSION csv' >> ${TMPDIR}/gmsd.style echo 'FIELD_DELIMITER TAB' >> ${TMPDIR}/gmsd.style echo 'RECORD_DELIMITER NEWLINE' >> ${TMPDIR}/gmsd.style @@ -92,3 +92,337 @@ gpsbabel -i unicsv -f ${REFERENCE}/gmsd.unicsv -o xcsv,style=${TMPDIR}/gmsd.styl compare ${REFERENCE}/gmsd.xcsv ${TMPDIR}/gmsd.xcsv gpsbabel -i xcsv,style=${TMPDIR}/gmsd.style -f ${REFERENCE}/gmsd.xcsv -o unicsv -F ${TMPDIR}/gmsd.unicsv compare ${REFERENCE}/gmsd.unicsv ${TMPDIR}/gmsd.unicsv + +if command -v tzselect >/dev/null 2>&1 ; then + export TZ='America/Denver' + echo " including xcsv timezone conversion test" + +# xcsv writer time handling + echo 'FIELD_DELIMITER COMMA' > ${TMPDIR}/datetime.style + echo 'RECORD_DELIMITER NEWLINE' >> ${TMPDIR}/datetime.style + echo 'PROLOGUE LAT_DECIMAL,LON_DECIMAL,EXCEL_TIME,GMT_TIME-12HR,GMT_TIME-DATE-24HR,GMT_TIME-24HR,LOCAL_TIME-12HR,LOCAL_TIME-DATE-24HR,LOCAL_TIME-24HR,ISO_TIME,ISO_TIME_MS,NET_TIME,TIMET_TIME,TIMET_TIME_MS,YYYYMMDD_TIME' >> ${TMPDIR}/datetime.style + echo 'IFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime.style + echo 'IFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime.style + echo 'IFIELD ISO_TIME, "", "%s"' >> ${TMPDIR}/datetime.style + echo 'OFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime.style + echo 'OFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime.style + echo 'OFIELD EXCEL_TIME, "", "%.12e"' >> ${TMPDIR}/datetime.style + echo 'OFIELD GMT_TIME, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime.style + echo 'OFIELD GMT_TIME, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime.style + echo 'OFIELD GMT_TIME, "", "%H:%M:%S"' >> ${TMPDIR}/datetime.style + echo 'OFIELD LOCAL_TIME, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime.style + echo 'OFIELD LOCAL_TIME, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime.style + echo 'OFIELD LOCAL_TIME, "", "%H:%M:%S"' >> ${TMPDIR}/datetime.style + echo 'OFIELD ISO_TIME, "", "%s"' >> ${TMPDIR}/datetime.style + echo 'OFIELD ISO_TIME_MS, "", "%s"' >> ${TMPDIR}/datetime.style + echo 'OFIELD NET_TIME, "", "%lld"' >> ${TMPDIR}/datetime.style + echo 'OFIELD TIMET_TIME, "", "%lld"' >> ${TMPDIR}/datetime.style + echo 'OFIELD TIMET_TIME_MS, "", "%lld"' >> ${TMPDIR}/datetime.style + echo 'OFIELD YYYYMMDD_TIME, "", "%ld"' >> ${TMPDIR}/datetime.style + gpsbabel -i xcsv,style=${TMPDIR}/datetime.style -f ${REFERENCE}/datetime.xcsv -o xcsv,style=${TMPDIR}/datetime.style -F ${TMPDIR}/datetime~xcsv.xcsv + compare ${REFERENCE}/datetime~xcsv.xcsv ${TMPDIR}/datetime~xcsv.xcsv + +# xcsv reader time handling + + echo 'FIELD_DELIMITER COMMA' > ${TMPDIR}/datetime_excel.style + echo 'RECORD_DELIMITER NEWLINE' >> ${TMPDIR}/datetime_excel.style + echo 'PROLOGUE LAT_DECIMAL,LON_DECIMAL,EXCEL_TIME,GMT_TIME-12HR,GMT_TIME-DATE-24HR,GMT_TIME-24HR,LOCAL_TIME-12HR,LOCAL_TIME-DATE-24HR,LOCAL_TIME-24HR,ISO_TIME,ISO_TIME_MS,NET_TIME,TIMET_TIME,TIMET_TIME_MS,YYYYMMDD_TIME' >> ${TMPDIR}/datetime_excel.style + echo 'OFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_excel.style + echo 'OFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_excel.style + echo 'OFIELD ISO_TIME, "", "%s"' >> ${TMPDIR}/datetime_excel.style + echo 'IFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_excel.style + echo 'IFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_excel.style + echo 'IFIELD EXCEL_TIME, "", "%.12e"' >> ${TMPDIR}/datetime_excel.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_excel.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_excel.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_excel.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_excel.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_excel.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_excel.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_excel.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_excel.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_excel.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_excel.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_excel.style + echo 'IFIELD IGNORE, "", "%ld"' >> ${TMPDIR}/datetime_excel.style + + gpsbabel -i xcsv,style=${TMPDIR}/datetime_excel.style -f ${REFERENCE}/datetime~xcsv.xcsv -o xcsv,style=${TMPDIR}/datetime_excel.style -F ${TMPDIR}/datetime_excel.xcsv + compare ${REFERENCE}/datetime_read.xcsv ${TMPDIR}/datetime_excel.xcsv + + echo 'FIELD_DELIMITER COMMA' > ${TMPDIR}/datetime_gmt.style + echo 'RECORD_DELIMITER NEWLINE' >> ${TMPDIR}/datetime_gmt.style + echo 'PROLOGUE LAT_DECIMAL,LON_DECIMAL,EXCEL_TIME,GMT_TIME-12HR,GMT_TIME-DATE-24HR,GMT_TIME-24HR,LOCAL_TIME-12HR,LOCAL_TIME-DATE-24HR,LOCAL_TIME-24HR,ISO_TIME,ISO_TIME_MS,NET_TIME,TIMET_TIME,TIMET_TIME_MS,YYYYMMDD_TIME' >> ${TMPDIR}/datetime_gmt.style + echo 'OFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_gmt.style + echo 'OFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_gmt.style + echo 'OFIELD ISO_TIME, "", "%s"' >> ${TMPDIR}/datetime_gmt.style + echo 'IFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_gmt.style + echo 'IFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_gmt.style + echo 'IFIELD IGNORE, "", "%.12e"' >> ${TMPDIR}/datetime_gmt.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_gmt.style + echo 'IFIELD GMT_TIME, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_gmt.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_gmt.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_gmt.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_gmt.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_gmt.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_gmt.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_gmt.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_gmt.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_gmt.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_gmt.style + echo 'IFIELD IGNORE, "", "%ld"' >> ${TMPDIR}/datetime_gmt.style + + gpsbabel -i xcsv,style=${TMPDIR}/datetime_gmt.style -f ${REFERENCE}/datetime~xcsv.xcsv -o xcsv,style=${TMPDIR}/datetime_gmt.style -F ${TMPDIR}/datetime_gmt.xcsv + compare ${REFERENCE}/datetime_read.xcsv ${TMPDIR}/datetime_gmt.xcsv + + echo 'FIELD_DELIMITER COMMA' > ${TMPDIR}/datetime_iso.style + echo 'RECORD_DELIMITER NEWLINE' >> ${TMPDIR}/datetime_iso.style + echo 'PROLOGUE LAT_DECIMAL,LON_DECIMAL,EXCEL_TIME,GMT_TIME-12HR,GMT_TIME-DATE-24HR,GMT_TIME-24HR,LOCAL_TIME-12HR,LOCAL_TIME-DATE-24HR,LOCAL_TIME-24HR,ISO_TIME,ISO_TIME_MS,NET_TIME,TIMET_TIME,TIMET_TIME_MS,YYYYMMDD_TIME' >> ${TMPDIR}/datetime_iso.style + echo 'OFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_iso.style + echo 'OFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_iso.style + echo 'OFIELD ISO_TIME, "", "%s"' >> ${TMPDIR}/datetime_iso.style + echo 'IFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_iso.style + echo 'IFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_iso.style + echo 'IFIELD IGNORE, "", "%.12e"' >> ${TMPDIR}/datetime_iso.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_iso.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_iso.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_iso.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_iso.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_iso.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_iso.style + echo 'IFIELD ISO_TIME, "", "%s"' >> ${TMPDIR}/datetime_iso.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_iso.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_iso.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_iso.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_iso.style + echo 'IFIELD IGNORE, "", "%ld"' >> ${TMPDIR}/datetime_iso.style + + gpsbabel -i xcsv,style=${TMPDIR}/datetime_iso.style -f ${REFERENCE}/datetime~xcsv.xcsv -o xcsv,style=${TMPDIR}/datetime_iso.style -F ${TMPDIR}/datetime_iso.xcsv + compare ${REFERENCE}/datetime_read.xcsv ${TMPDIR}/datetime_iso.xcsv + + echo 'FIELD_DELIMITER COMMA' > ${TMPDIR}/datetime_isoms.style + echo 'RECORD_DELIMITER NEWLINE' >> ${TMPDIR}/datetime_isoms.style + echo 'PROLOGUE LAT_DECIMAL,LON_DECIMAL,EXCEL_TIME,GMT_TIME-12HR,GMT_TIME-DATE-24HR,GMT_TIME-24HR,LOCAL_TIME-12HR,LOCAL_TIME-DATE-24HR,LOCAL_TIME-24HR,ISO_TIME,ISO_TIME_MS,NET_TIME,TIMET_TIME,TIMET_TIME_MS,YYYYMMDD_TIME' >> ${TMPDIR}/datetime_isoms.style + echo 'OFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_isoms.style + echo 'OFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_isoms.style + echo 'OFIELD ISO_TIME, "", "%s"' >> ${TMPDIR}/datetime_isoms.style + echo 'IFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_isoms.style + echo 'IFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_isoms.style + echo 'IFIELD IGNORE, "", "%.12e"' >> ${TMPDIR}/datetime_isoms.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_isoms.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_isoms.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_isoms.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_isoms.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_isoms.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_isoms.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_isoms.style + echo 'IFIELD ISO_TIME_MS, "", "%s"' >> ${TMPDIR}/datetime_isoms.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_isoms.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_isoms.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_isoms.style + echo 'IFIELD IGNORE, "", "%ld"' >> ${TMPDIR}/datetime_isoms.style + + gpsbabel -i xcsv,style=${TMPDIR}/datetime_isoms.style -f ${REFERENCE}/datetime~xcsv.xcsv -o xcsv,style=${TMPDIR}/datetime_isoms.style -F ${TMPDIR}/datetime_isoms.xcsv + compare ${REFERENCE}/datetime_read.xcsv ${TMPDIR}/datetime_isoms.xcsv + + echo 'FIELD_DELIMITER COMMA' > ${TMPDIR}/datetime_local.style + echo 'RECORD_DELIMITER NEWLINE' >> ${TMPDIR}/datetime_local.style + echo 'PROLOGUE LAT_DECIMAL,LON_DECIMAL,EXCEL_TIME,GMT_TIME-12HR,GMT_TIME-DATE-24HR,GMT_TIME-24HR,LOCAL_TIME-12HR,LOCAL_TIME-DATE-24HR,LOCAL_TIME-24HR,ISO_TIME,ISO_TIME_MS,NET_TIME,TIMET_TIME,TIMET_TIME_MS,YYYYMMDD_TIME' >> ${TMPDIR}/datetime_local.style + echo 'OFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_local.style + echo 'OFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_local.style + echo 'OFIELD ISO_TIME, "", "%s"' >> ${TMPDIR}/datetime_local.style + echo 'IFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_local.style + echo 'IFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_local.style + echo 'IFIELD IGNORE, "", "%.12e"' >> ${TMPDIR}/datetime_local.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_local.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_local.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_local.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_local.style + echo 'IFIELD LOCAL_TIME, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_local.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_local.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_local.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_local.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_local.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_local.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_local.style + echo 'IFIELD IGNORE, "", "%ld"' >> ${TMPDIR}/datetime_local.style + + gpsbabel -i xcsv,style=${TMPDIR}/datetime_local.style -f ${REFERENCE}/datetime~xcsv.xcsv -o xcsv,style=${TMPDIR}/datetime_local.style -F ${TMPDIR}/datetime_local.xcsv + compare ${REFERENCE}/datetime_read.xcsv ${TMPDIR}/datetime_local.xcsv + + echo 'FIELD_DELIMITER COMMA' > ${TMPDIR}/datetime_net.style + echo 'RECORD_DELIMITER NEWLINE' >> ${TMPDIR}/datetime_net.style + echo 'PROLOGUE LAT_DECIMAL,LON_DECIMAL,EXCEL_TIME,GMT_TIME-12HR,GMT_TIME-DATE-24HR,GMT_TIME-24HR,LOCAL_TIME-12HR,LOCAL_TIME-DATE-24HR,LOCAL_TIME-24HR,ISO_TIME,ISO_TIME_MS,NET_TIME,TIMET_TIME,TIMET_TIME_MS,YYYYMMDD_TIME' >> ${TMPDIR}/datetime_net.style + echo 'OFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_net.style + echo 'OFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_net.style + echo 'OFIELD ISO_TIME, "", "%s"' >> ${TMPDIR}/datetime_net.style + echo 'IFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_net.style + echo 'IFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_net.style + echo 'IFIELD IGNORE, "", "%.12e"' >> ${TMPDIR}/datetime_net.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_net.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_net.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_net.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_net.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_net.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_net.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_net.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_net.style + echo 'IFIELD NET_TIME, "", "%lld"' >> ${TMPDIR}/datetime_net.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_net.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_net.style + echo 'IFIELD IGNORE, "", "%ld"' >> ${TMPDIR}/datetime_net.style + + gpsbabel -i xcsv,style=${TMPDIR}/datetime_net.style -f ${REFERENCE}/datetime~xcsv.xcsv -o xcsv,style=${TMPDIR}/datetime_net.style -F ${TMPDIR}/datetime_net.xcsv + compare ${REFERENCE}/datetime_read.xcsv ${TMPDIR}/datetime_net.xcsv + + echo 'FIELD_DELIMITER COMMA' > ${TMPDIR}/datetime_timet.style + echo 'RECORD_DELIMITER NEWLINE' >> ${TMPDIR}/datetime_timet.style + echo 'PROLOGUE LAT_DECIMAL,LON_DECIMAL,EXCEL_TIME,GMT_TIME-12HR,GMT_TIME-DATE-24HR,GMT_TIME-24HR,LOCAL_TIME-12HR,LOCAL_TIME-DATE-24HR,LOCAL_TIME-24HR,ISO_TIME,ISO_TIME_MS,NET_TIME,TIMET_TIME,TIMET_TIME_MS,YYYYMMDD_TIME' >> ${TMPDIR}/datetime_timet.style + echo 'OFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_timet.style + echo 'OFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_timet.style + echo 'OFIELD ISO_TIME, "", "%s"' >> ${TMPDIR}/datetime_timet.style + echo 'IFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_timet.style + echo 'IFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_timet.style + echo 'IFIELD IGNORE, "", "%.12e"' >> ${TMPDIR}/datetime_timet.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_timet.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_timet.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_timet.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_timet.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_timet.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_timet.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_timet.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_timet.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_timet.style + echo 'IFIELD TIMET_TIME, "", "%lld"' >> ${TMPDIR}/datetime_timet.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_timet.style + echo 'IFIELD IGNORE, "", "%ld"' >> ${TMPDIR}/datetime_timet.style + + gpsbabel -i xcsv,style=${TMPDIR}/datetime_timet.style -f ${REFERENCE}/datetime~xcsv.xcsv -o xcsv,style=${TMPDIR}/datetime_timet.style -F ${TMPDIR}/datetime_timet.xcsv + compare ${REFERENCE}/datetime_read.xcsv ${TMPDIR}/datetime_timet.xcsv + + echo 'FIELD_DELIMITER COMMA' > ${TMPDIR}/datetime_timetms.style + echo 'RECORD_DELIMITER NEWLINE' >> ${TMPDIR}/datetime_timetms.style + echo 'PROLOGUE LAT_DECIMAL,LON_DECIMAL,EXCEL_TIME,GMT_TIME-12HR,GMT_TIME-DATE-24HR,GMT_TIME-24HR,LOCAL_TIME-12HR,LOCAL_TIME-DATE-24HR,LOCAL_TIME-24HR,ISO_TIME,ISO_TIME_MS,NET_TIME,TIMET_TIME,TIMET_TIME_MS,YYYYMMDD_TIME' >> ${TMPDIR}/datetime_timetms.style + echo 'OFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_timetms.style + echo 'OFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_timetms.style + echo 'OFIELD ISO_TIME, "", "%s"' >> ${TMPDIR}/datetime_timetms.style + echo 'IFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_timetms.style + echo 'IFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_timetms.style + echo 'IFIELD IGNORE, "", "%.12e"' >> ${TMPDIR}/datetime_timetms.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_timetms.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_timetms.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_timetms.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_timetms.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_timetms.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_timetms.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_timetms.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_timetms.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_timetms.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_timetms.style + echo 'IFIELD TIMET_TIME_MS, "", "%lld"' >> ${TMPDIR}/datetime_timetms.style + echo 'IFIELD IGNORE, "", "%ld"' >> ${TMPDIR}/datetime_timetms.style + + gpsbabel -i xcsv,style=${TMPDIR}/datetime_timetms.style -f ${REFERENCE}/datetime~xcsv.xcsv -o xcsv,style=${TMPDIR}/datetime_timetms.style -F ${TMPDIR}/datetime_timetms.xcsv + compare ${REFERENCE}/datetime_read.xcsv ${TMPDIR}/datetime_timetms.xcsv + + echo 'FIELD_DELIMITER COMMA' > ${TMPDIR}/datetime_hmsg.style + echo 'RECORD_DELIMITER NEWLINE' >> ${TMPDIR}/datetime_hmsg.style + echo 'PROLOGUE LAT_DECIMAL,LON_DECIMAL,EXCEL_TIME,GMT_TIME-12HR,GMT_TIME-DATE-24HR,GMT_TIME-24HR,LOCAL_TIME-12HR,LOCAL_TIME-DATE-24HR,LOCAL_TIME-24HR,ISO_TIME,ISO_TIME_MS,NET_TIME,TIMET_TIME,TIMET_TIME_MS,YYYYMMDD_TIME' >> ${TMPDIR}/datetime_hmsg.style + echo 'OFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_hmsg.style + echo 'OFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_hmsg.style + echo 'OFIELD ISO_TIME, "", "%s"' >> ${TMPDIR}/datetime_hmsg.style + echo 'IFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_hmsg.style + echo 'IFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_hmsg.style + echo 'IFIELD IGNORE, "", "%.12e"' >> ${TMPDIR}/datetime_hmsg.style + echo 'IFIELD GMT_TIME, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_hmsg.style + echo 'IFIELD GMT_TIME, "", "%Y-%m-%d"' >> ${TMPDIR}/datetime_hmsg.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_hmsg.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_hmsg.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_hmsg.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_hmsg.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_hmsg.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_hmsg.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_hmsg.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_hmsg.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_hmsg.style + echo 'IFIELD IGNORE, "", "%ld"' >> ${TMPDIR}/datetime_hmsg.style + + gpsbabel -i xcsv,style=${TMPDIR}/datetime_hmsg.style -f ${REFERENCE}/datetime~xcsv.xcsv -o xcsv,style=${TMPDIR}/datetime_hmsg.style -F ${TMPDIR}/datetime_hmsg.xcsv + compare ${REFERENCE}/datetime_read.xcsv ${TMPDIR}/datetime_hmsg.xcsv + + echo 'FIELD_DELIMITER COMMA' > ${TMPDIR}/datetime_hmsg2.style + echo 'RECORD_DELIMITER NEWLINE' >> ${TMPDIR}/datetime_hmsg2.style + echo 'PROLOGUE LAT_DECIMAL,LON_DECIMAL,EXCEL_TIME,GMT_TIME-12HR,GMT_TIME-DATE-24HR,GMT_TIME-24HR,LOCAL_TIME-12HR,LOCAL_TIME-DATE-24HR,LOCAL_TIME-24HR,ISO_TIME,ISO_TIME_MS,NET_TIME,TIMET_TIME,TIMET_TIME_MS,YYYYMMDD_TIME' >> ${TMPDIR}/datetime_hmsg2.style + echo 'OFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_hmsg2.style + echo 'OFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_hmsg2.style + echo 'OFIELD ISO_TIME, "", "%s"' >> ${TMPDIR}/datetime_hmsg2.style + echo 'IFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_hmsg2.style + echo 'IFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_hmsg2.style + echo 'IFIELD IGNORE, "", "%.12e"' >> ${TMPDIR}/datetime_hmsg2.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_hmsg2.style + echo 'IFIELD GMT_TIME, "", "%Y-%m-%d"' >> ${TMPDIR}/datetime_hmsg2.style + echo 'IFIELD GMT_TIME, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_hmsg2.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_hmsg2.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_hmsg2.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_hmsg2.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_hmsg2.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_hmsg2.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_hmsg2.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_hmsg2.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_hmsg2.style + echo 'IFIELD IGNORE, "", "%ld"' >> ${TMPDIR}/datetime_hmsg2.style + + gpsbabel -i xcsv,style=${TMPDIR}/datetime_hmsg2.style -f ${REFERENCE}/datetime~xcsv.xcsv -o xcsv,style=${TMPDIR}/datetime_hmsg2.style -F ${TMPDIR}/datetime_hmsg2.xcsv + compare ${REFERENCE}/datetime_read.xcsv ${TMPDIR}/datetime_hmsg2.xcsv + + echo 'FIELD_DELIMITER COMMA' > ${TMPDIR}/datetime_hmsl.style + echo 'RECORD_DELIMITER NEWLINE' >> ${TMPDIR}/datetime_hmsl.style + echo 'PROLOGUE LAT_DECIMAL,LON_DECIMAL,EXCEL_TIME,GMT_TIME-12HR,GMT_TIME-DATE-24HR,GMT_TIME-24HR,LOCAL_TIME-12HR,LOCAL_TIME-DATE-24HR,LOCAL_TIME-24HR,ISO_TIME,ISO_TIME_MS,NET_TIME,TIMET_TIME,TIMET_TIME_MS,YYYYMMDD_TIME' >> ${TMPDIR}/datetime_hmsl.style + echo 'OFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_hmsl.style + echo 'OFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_hmsl.style + echo 'OFIELD ISO_TIME, "", "%s"' >> ${TMPDIR}/datetime_hmsl.style + echo 'IFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_hmsl.style + echo 'IFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_hmsl.style + echo 'IFIELD IGNORE, "", "%.12e"' >> ${TMPDIR}/datetime_hmsl.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_hmsl.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_hmsl.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_hmsl.style + echo 'IFIELD LOCAL_TIME, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_hmsl.style + echo 'IFIELD LOCAL_TIME, "", "%Y-%m-%d"' >> ${TMPDIR}/datetime_hmsl.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_hmsl.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_hmsl.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_hmsl.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_hmsl.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_hmsl.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_hmsl.style + echo 'IFIELD IGNORE, "", "%ld"' >> ${TMPDIR}/datetime_hmsl.style + + gpsbabel -i xcsv,style=${TMPDIR}/datetime_hmsl.style -f ${REFERENCE}/datetime~xcsv.xcsv -o xcsv,style=${TMPDIR}/datetime_hmsl.style -F ${TMPDIR}/datetime_hmsl.xcsv + compare ${REFERENCE}/datetime_read.xcsv ${TMPDIR}/datetime_hmsl.xcsv + + echo 'FIELD_DELIMITER COMMA' > ${TMPDIR}/datetime_hmsl2.style + echo 'RECORD_DELIMITER NEWLINE' >> ${TMPDIR}/datetime_hmsl2.style + echo 'PROLOGUE LAT_DECIMAL,LON_DECIMAL,EXCEL_TIME,GMT_TIME-12HR,GMT_TIME-DATE-24HR,GMT_TIME-24HR,LOCAL_TIME-12HR,LOCAL_TIME-DATE-24HR,LOCAL_TIME-24HR,ISO_TIME,ISO_TIME_MS,NET_TIME,TIMET_TIME,TIMET_TIME_MS,YYYYMMDD_TIME' >> ${TMPDIR}/datetime_hmsl2.style + echo 'OFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_hmsl2.style + echo 'OFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_hmsl2.style + echo 'OFIELD ISO_TIME, "", "%s"' >> ${TMPDIR}/datetime_hmsl2.style + echo 'IFIELD LAT_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_hmsl2.style + echo 'IFIELD LON_DECIMAL,"","%.6f"' >> ${TMPDIR}/datetime_hmsl2.style + echo 'IFIELD IGNORE, "", "%.12e"' >> ${TMPDIR}/datetime_hmsl2.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_hmsl2.style + echo 'IFIELD IGNORE, "", "%Y-%m-%d %H:%M:%S"' >> ${TMPDIR}/datetime_hmsl2.style + echo 'IFIELD IGNORE, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_hmsl2.style + echo 'IFIELD IGNORE, "", "%I:%M:%S %p"' >> ${TMPDIR}/datetime_hmsl2.style + echo 'IFIELD LOCAL_TIME, "", "%Y-%m-%d"' >> ${TMPDIR}/datetime_hmsl2.style + echo 'IFIELD LOCAL_TIME, "", "%H:%M:%S"' >> ${TMPDIR}/datetime_hmsl2.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_hmsl2.style + echo 'IFIELD IGNORE, "", "%s"' >> ${TMPDIR}/datetime_hmsl2.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_hmsl2.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_hmsl2.style + echo 'IFIELD IGNORE, "", "%lld"' >> ${TMPDIR}/datetime_hmsl2.style + echo 'IFIELD IGNORE, "", "%ld"' >> ${TMPDIR}/datetime_hmsl2.style + + gpsbabel -i xcsv,style=${TMPDIR}/datetime_hmsl2.style -f ${REFERENCE}/datetime~xcsv.xcsv -o xcsv,style=${TMPDIR}/datetime_hmsl2.style -F ${TMPDIR}/datetime_hmsl2.xcsv + compare ${REFERENCE}/datetime_read.xcsv ${TMPDIR}/datetime_hmsl2.xcsv + + unset -v TZ +fi diff --git a/unicsv.cc b/unicsv.cc index 03c3147c4..480616934 100644 --- a/unicsv.cc +++ b/unicsv.cc @@ -23,8 +23,7 @@ #include // for fabs, lround #include // for NULL, sscanf -#include // for strchr, strncpy -#include // for gmtime +#include // for tm #include // for QByteArray #include // for QChar @@ -225,7 +224,7 @@ UnicsvFormat::unicsv_parse_gc_code(const QString& str) return res; } -time_t +QDate UnicsvFormat::unicsv_parse_date(const char* str, int* consumed) { int p1, p2, p3; @@ -240,9 +239,9 @@ UnicsvFormat::unicsv_parse_date(const char* str, int* consumed) if (ct != 5) { if (consumed) { /* don't stop here; it's only sniffing */ *consumed = 0; /* for a possible date */ - return 0; + return {}; } - fatal(FatalMsg() << MYNAME << ": Could not parse date string (" << str << ").\n"); + fatal(FatalMsg() << MYNAME << ": Could not parse date string (" << str << ")."); } if ((p1 > 99) || (sep[0] == '-')) { /* Y-M-D (iso like) */ @@ -269,56 +268,59 @@ UnicsvFormat::unicsv_parse_date(const char* str, int* consumed) if ((tm.tm_mon > 12) || (tm.tm_mon < 1) || (tm.tm_mday > 31) || (tm.tm_mday < 1)) { if (consumed) { *consumed = 0; - return 0; /* don't stop here */ + return {}; /* don't stop here */ } - fatal(FatalMsg() << MYNAME << ": Could not parse date string (" << str << ").\n"); + fatal(FatalMsg() << MYNAME << ": Could not parse date string (" << str << ")."); } - tm.tm_year -= 1900; - tm.tm_mon -= 1; - - return mkgmtime(&tm); + QDate result{tm.tm_year, tm.tm_mon, tm.tm_mday}; + if (!result.isValid()) { + fatal(FatalMsg() << MYNAME << ": Invalid date parsed from string (" << str << ")."); + } + return result; } -time_t -UnicsvFormat::unicsv_parse_time(const char* str, int* usec, time_t* date) +QTime +UnicsvFormat::unicsv_parse_time(const char* str, QDate& date) { - int hour, min, sec; + int hour; + int min; + int sec; + int msec; int consumed = 0; - double us; - char sep[2]; + double frac_sec; /* If we have something we're pretty sure is a date, parse that * first, skip over it, and pass that back to the caller) */ - time_t ldate = unicsv_parse_date(str, &consumed); - if (consumed && ldate) { + QDate ldate = unicsv_parse_date(str, &consumed); + if (consumed && ldate.isValid()) { str += consumed; - if (date) { - *date = ldate; - } + date = ldate; } - int ct = sscanf(str, "%d%1[.://]%d%1[.://]%d%lf", &hour, sep, &min, sep, &sec, &us); - if (ct < 5) { - fatal(MYNAME ": Could not parse time string (%s).\n", str); + int ct = sscanf(str, "%d%*1[.://]%d%*1[.://]%d%lf", &hour, &min, &sec, &frac_sec); + if (ct < 3) { + fatal(FatalMsg() << MYNAME << ": Could not parse time string (" << str << ")."); } - if (ct == 6) { - *usec = lround((us * 1000000)); - if (*usec > 999999) { - *usec = 0; - sec++; - } + if (ct >= 4) { + // Don't round up and ripple through seconds, minutes, hours. + // 23:59:59.9999999 -> 24:00:00.000 which is an invalid QTime. + msec = frac_sec * 1000.0; } else { - *usec = 0; + msec = 0; } - return ((hour * SECONDS_PER_HOUR) + (min * 60) + sec); + QTime result{hour, min, sec, msec}; + if (!result.isValid()) { + fatal(FatalMsg() << MYNAME << ": Invalid time parsed from string (" << str << ")."); + } + return result; } -time_t -UnicsvFormat::unicsv_parse_time(const QString& str, int* msec, time_t* date) +QTime +UnicsvFormat::unicsv_parse_time(const QString& str, QDate& date) { - return unicsv_parse_time(CSTR(str), msec, date); + return unicsv_parse_time(CSTR(str), date); } Geocache::status_t @@ -338,19 +340,9 @@ UnicsvFormat::unicsv_parse_status(const QString& str) } QDateTime -UnicsvFormat::unicsv_adjust_time(const time_t time, const time_t* date) const +UnicsvFormat::unicsv_adjust_time(const QDate date, const QTime time, bool is_localtime) const { - time_t res = time; - if (date) { - res += *date; - } - if (opt_utc) { - res += xstrtoi(opt_utc, nullptr, 10) * SECONDS_PER_HOUR; - } else { - std::tm tm = *gmtime(&res); - res = mklocaltime(&tm); - } - return QDateTime::fromSecsSinceEpoch(res, Qt::UTC); + return make_datetime(date, time, is_localtime, opt_utc != nullptr, utc_offset); } bool @@ -470,6 +462,8 @@ UnicsvFormat::rd_init(const QString& fname) } else { unicsv_fieldsep = nullptr; } + + utc_offset = (opt_utc == nullptr)? 0 : xstrtoi(opt_utc, nullptr, 10) * SECONDS_PER_HOUR; } void @@ -495,10 +489,11 @@ UnicsvFormat::unicsv_parse_one_line(const QString& ibuf) double swiss_easting = kUnicsvUnknown; double swiss_northing = kUnicsvUnknown; int checked = 0; - time_t date = -1; - time_t time = -1; - int usec = -1; - char is_localtime = 0; + QDate local_date; + QTime local_time; + QDate utc_date; + QTime utc_time; + bool need_datetime = true; garmin_fs_t* gmsd; double d; std::tm ymd{}; @@ -682,16 +677,14 @@ UnicsvFormat::unicsv_parse_one_line(const QString& ibuf) break; case fld_utc_date: - if ((is_localtime < 2) && (date < 0)) { - date = unicsv_parse_date(CSTR(value), nullptr); - is_localtime = 0; + if (need_datetime && !utc_date.isValid()) { + utc_date = unicsv_parse_date(CSTR(value), nullptr); } break; case fld_utc_time: - if ((is_localtime < 2) && (time < 0)) { - time = unicsv_parse_time(value, &usec, &date); - is_localtime = 0; + if (need_datetime && !utc_time.isValid()) { + utc_time = unicsv_parse_time(value, utc_date); } break; @@ -763,21 +756,19 @@ UnicsvFormat::unicsv_parse_one_line(const QString& ibuf) break; case fld_iso_time: - is_localtime = 2; /* fix result */ - wpt->SetCreationTime(xml_parse_time(value)); + need_datetime = false; /* fix result */ + wpt->SetCreationTime(QDateTime::fromString(value, Qt::ISODateWithMs)); break; case fld_time: - if ((is_localtime < 2) && (time < 0)) { - time = unicsv_parse_time(value, &usec, &date); - is_localtime = 1; + if (need_datetime && !local_time.isValid()) { + local_time = unicsv_parse_time(value, local_date); } break; case fld_date: - if ((is_localtime < 2) && (date < 0)) { - date = unicsv_parse_date(CSTR(value), nullptr); - is_localtime = 1; + if (need_datetime && !local_date.isValid()) { + local_date = unicsv_parse_date(CSTR(value), nullptr); } break; @@ -806,9 +797,8 @@ UnicsvFormat::unicsv_parse_one_line(const QString& ibuf) break; case fld_datetime: - if ((is_localtime < 2) && (date < 0) && (time < 0)) { - time = unicsv_parse_time(value, &usec, &date); - is_localtime = 1; + if (need_datetime && !local_date.isValid() && !local_time.isValid()) { + local_time = unicsv_parse_time(value, local_date); } break; @@ -918,20 +908,20 @@ UnicsvFormat::unicsv_parse_one_line(const QString& ibuf) gc_data->is_available = unicsv_parse_status(value); break; case fld_gc_exported: { - time_t etime, edate; - int eusec; - etime = unicsv_parse_time(value, &eusec, &edate); - if (edate || etime) { - gc_data->exported = unicsv_adjust_time(etime, &edate); + QTime etime; + QDate edate; + etime = unicsv_parse_time(value, edate); + if (edate.isValid() || etime.isValid()) { + gc_data->exported = unicsv_adjust_time(edate, etime, true); } } break; case fld_gc_last_found: { - time_t ftime, fdate; - int fusec; - ftime = unicsv_parse_time(value, &fusec, &fdate); - if (fdate || ftime) { - gc_data->last_found = unicsv_adjust_time(ftime, &fdate); + QTime ftime; + QDate fdate; + ftime = unicsv_parse_time(value, fdate); + if (fdate.isValid() || ftime.isValid()) { + gc_data->last_found = unicsv_adjust_time(fdate, ftime, true); } } break; @@ -960,24 +950,19 @@ UnicsvFormat::unicsv_parse_one_line(const QString& ibuf) return; } - if (is_localtime < 2) { /* not fixed */ - if ((time >= 0) && (date >= 0)) { - time_t t = date + time; - - if (is_localtime) { - std::tm tm = *gmtime(&t); - if (opt_utc) { - wpt->SetCreationTime(mkgmtime(&tm)); - } else { - wpt->SetCreationTime(mklocaltime(&tm)); - } - } else { - wpt->SetCreationTime(t); - } - } else if (time >= 0) { - wpt->SetCreationTime(time); - } else if (date >= 0) { - wpt->SetCreationTime(date); + if (need_datetime) { /* not fixed */ + if (utc_date.isValid() && utc_time.isValid()) { + wpt->SetCreationTime(unicsv_adjust_time(utc_date, utc_time, false)); + } else if (local_date.isValid() && local_time.isValid()) { + wpt->SetCreationTime(unicsv_adjust_time(local_date, local_time, true)); + } else if (utc_date.isValid()) { + wpt->SetCreationTime(unicsv_adjust_time(utc_date, utc_time, false)); + } else if (local_date.isValid()) { + wpt->SetCreationTime(unicsv_adjust_time(local_date, local_time, true)); + } else if (utc_time.isValid()) { + wpt->SetCreationTime(unicsv_adjust_time(utc_date, utc_time, false)); + } else if (local_time.isValid()) { + wpt->SetCreationTime(unicsv_adjust_time(local_date, local_time, true)); } else if (ymd.tm_year || ymd.tm_mon || ymd.tm_mday) { if (ymd.tm_year < 100) { if (ymd.tm_year <= 70) { @@ -986,7 +971,6 @@ UnicsvFormat::unicsv_parse_one_line(const QString& ibuf) ymd.tm_year += 1900; } } - ymd.tm_year -= 1900; if (ymd.tm_mon == 0) { ymd.tm_mon = 1; @@ -995,27 +979,17 @@ UnicsvFormat::unicsv_parse_one_line(const QString& ibuf) ymd.tm_mday = 1; } - ymd.tm_mon--; - if (opt_utc) { - wpt->SetCreationTime(mkgmtime(&ymd)); - } else { - wpt->SetCreationTime(mklocaltime(&ymd)); - } + wpt->SetCreationTime(unicsv_adjust_time( + QDate(ymd.tm_year, ymd.tm_mon, ymd.tm_mday), + QTime(ymd.tm_hour, ymd.tm_min, ymd.tm_sec), + true)); } else if (ymd.tm_hour || ymd.tm_min || ymd.tm_sec) { - if (opt_utc) { - wpt->SetCreationTime(mkgmtime(&ymd)); - } else { - wpt->SetCreationTime(mklocaltime(&ymd)); - } - } - - if (usec >= 0) { - wpt->creation_time = wpt->creation_time.addMSecs(MICRO_TO_MILLI(usec)); + wpt->SetCreationTime(unicsv_adjust_time( + QDate(), + QTime(ymd.tm_hour, ymd.tm_min, ymd.tm_sec), + true)); } - if (opt_utc) { - wpt->creation_time = wpt->creation_time.addSecs(xstrtoi(opt_utc, nullptr, 10) * SECONDS_PER_HOUR); - } } /* utm/bng/swiss can be optional */ @@ -1131,16 +1105,16 @@ UnicsvFormat::unicsv_print_str(const QString& s) const } void -UnicsvFormat::unicsv_print_data_time(const QDateTime& idt) const +UnicsvFormat::unicsv_print_date_time(const QDateTime& idt) const { if (!idt.isValid()) { return; } - QDateTime dt = idt; - if (opt_utc) { - //time += xstrtoi(opt_utc, nullptr, 10) * SECONDS_PER_HOUR; - dt = dt.addSecs(xstrtoi(opt_utc, nullptr, 10) * SECONDS_PER_HOUR); - dt = dt.toUTC(); + QDateTime dt; + if (opt_utc != nullptr) { + dt = idt.toOffsetFromUtc(utc_offset); + } else { + dt = idt.toLocalTime(); } unicsv_print_str(dt.toString(u"yyyy/MM/dd hh:mm:ss")); @@ -1174,7 +1148,7 @@ UnicsvFormat::unicsv_waypt_enum_cb(const Waypoint* wpt) } if (wpt->creation_time.isValid()) { unicsv_outp_flags[fld_time] = true; - if (wpt->creation_time.toTime_t() >= SECONDS_PER_DAY) { + if (wpt->creation_time.toTime_t() >= 2 * SECONDS_PER_DAY) { unicsv_outp_flags[fld_date] = true; } } @@ -1524,12 +1498,10 @@ UnicsvFormat::unicsv_waypt_disp_cb(const Waypoint* wpt) } } if (unicsv_outp_flags[fld_date]) { - if (wpt->creation_time.toTime_t() >= SECONDS_PER_DAY) { + if (wpt->creation_time.toTime_t() >= 2 * SECONDS_PER_DAY) { QDateTime dt; - if (opt_utc) { - dt = wpt->GetCreationTime().toUTC(); - // We might wrap to a different day by overriding the TZ offset. - dt = dt.addSecs(xstrtoi(opt_utc, nullptr, 10) * SECONDS_PER_HOUR); + if (opt_utc != nullptr) { + dt = wpt->GetCreationTime().toOffsetFromUtc(utc_offset); } else { dt = wpt->GetCreationTime().toLocalTime(); } @@ -1541,18 +1513,17 @@ UnicsvFormat::unicsv_waypt_disp_cb(const Waypoint* wpt) } if (unicsv_outp_flags[fld_time]) { if (wpt->creation_time.isValid()) { - QTime t; - if (opt_utc) { - t = wpt->GetCreationTime().toUTC().time(); - t = t.addSecs(xstrtoi(opt_utc, nullptr, 10) * SECONDS_PER_HOUR); + QDateTime dt; + if (opt_utc != nullptr) { + dt = wpt->GetCreationTime().toOffsetFromUtc(utc_offset); } else { - t = wpt->GetCreationTime().toLocalTime().time(); + dt = wpt->GetCreationTime().toLocalTime(); } QString out; - if (t.msec() > 0) { - out = t.toString(u"hh:mm:ss.zzz"); + if (dt.time().msec() > 0) { + out = dt.toString(u"hh:mm:ss.zzz"); } else { - out = t.toString(u"hh:mm:ss"); + out = dt.toString(u"hh:mm:ss"); } *fout << unicsv_fieldsep << out; } else { @@ -1653,14 +1624,14 @@ UnicsvFormat::unicsv_waypt_disp_cb(const Waypoint* wpt) } if (unicsv_outp_flags[fld_gc_exported]) { if (gc_data) { - unicsv_print_data_time(gc_data->exported); + unicsv_print_date_time(gc_data->exported); } else { *fout << unicsv_fieldsep; } } if (unicsv_outp_flags[fld_gc_last_found]) { if (gc_data) { - unicsv_print_data_time(gc_data->last_found); + unicsv_print_date_time(gc_data->last_found); } else { *fout << unicsv_fieldsep; } @@ -1742,6 +1713,7 @@ UnicsvFormat::wr_init(const QString& fname) } llprec = xstrtoi(opt_prec, nullptr, 10); + utc_offset = (opt_utc == nullptr)? 0 : xstrtoi(opt_utc, nullptr, 10) * SECONDS_PER_HOUR; } void diff --git a/unicsv.h b/unicsv.h index 8a8c8aac6..ce564508d 100644 --- a/unicsv.h +++ b/unicsv.h @@ -23,10 +23,11 @@ #include // for bitset #include // for uint32_t -#include // for gmtime +#include // for QDate #include // for QDateTime #include // for QString +#include // for QTime #include // for QVector #include "defs.h" @@ -165,17 +166,17 @@ private: /* Member Functions */ static long long int unicsv_parse_gc_code(const QString& str); - static time_t unicsv_parse_date(const char* str, int* consumed); - static time_t unicsv_parse_time(const char* str, int* usec, time_t* date); - static time_t unicsv_parse_time(const QString& str, int* msec, time_t* date); + static QDate unicsv_parse_date(const char* str, int* consumed); + static QTime unicsv_parse_time(const char* str, QDate& date); + static QTime unicsv_parse_time(const QString& str, QDate& date); static Geocache::status_t unicsv_parse_status(const QString& str); - QDateTime unicsv_adjust_time(time_t time, const time_t* date) const; + QDateTime unicsv_adjust_time(const QDate date, const QTime time, bool is_localtime) const; static bool unicsv_compare_fields(const QString& s, const field_t* f); void unicsv_fondle_header(QString header); void unicsv_parse_one_line(const QString& ibuf); void unicsv_fatal_outside(const Waypoint* wpt) const; void unicsv_print_str(const QString& s) const; - void unicsv_print_data_time(const QDateTime& idt) const; + void unicsv_print_date_time(const QDateTime& idt) const; void unicsv_waypt_enum_cb(const Waypoint* wpt); void unicsv_waypt_disp_cb(const Waypoint* wpt); static void unicsv_check_modes(bool test); @@ -208,6 +209,7 @@ private: int unicsv_waypt_ct{}; char unicsv_detect{}; int llprec{}; + int utc_offset{}; QVector unicsv_args = { { @@ -220,7 +222,7 @@ private: }, { "utc", &opt_utc, "Write timestamps with offset x to UTC time", - nullptr, ARGTYPE_INT, "-23", "+23", nullptr + nullptr, ARGTYPE_INT, "-14", "+14", nullptr }, { "format", &opt_format, "Write name(s) of format(s) from input session(s)", diff --git a/util.cc b/util.cc index 598891d76..349549195 100644 --- a/util.cc +++ b/util.cc @@ -26,10 +26,8 @@ #include // for fabs, floor #include // for va_list, va_end, va_start, va_copy #include // for size_t, vsnprintf, FILE, fopen, printf, sprintf, stderr, stdin, stdout -#include // for uint32_t #include // for abs, calloc, free, malloc, realloc #include // for strlen, strcat, strstr, memcpy, strcmp, strcpy, strdup, strchr, strerror -#include // for mktime, localtime #include // for QByteArray #include // for QChar, operator<=, operator>= @@ -447,67 +445,46 @@ le_write32(void* ptr, const unsigned value) p[3] = value >> 24; } -/* - mkgmtime -- convert tm struct in UTC to time_t - - works just like mktime but without all the mucking - around with timezones and daylight savings - - Borrowed from lynx GPL source code - http://lynx.isc.org/release/lynx2-8-5/src/mktime.c - - Written by Philippe De Muyter . -*/ - -time_t -mkgmtime(std::tm* time) +QDateTime +make_datetime(QDate date, QTime time, bool is_localtime, bool force_utc, int utc_offset) { - static const int m_to_d[12] = - {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; - - short month = time->tm_mon; - short year = time->tm_year + month / 12 + 1900; - month %= 12; - if (month < 0) { - year -= 1; - month += 12; - } - time_t result = (year - 1970) * 365 + m_to_d[month]; - if (month <= 1) { - year -= 1; + QDateTime result; + Qt::TimeSpec timespec; + int offset = 0; + + if (is_localtime) { + if (force_utc) { // override with passed option value + if (utc_offset == 0) { + // Qt 6.5.0 QDate::startOfDay(Qt::OffsetFromUTC, 0) returns an invalid QDateTime. + timespec = Qt::UTC; + } else { + timespec = Qt::OffsetFromUTC; + // Avoid Qt 6.5.0 warnings with non-zero offsets when not using Qt::OffsetFromUTC. + offset = utc_offset; + } + } else { + timespec = Qt::LocalTime; + } + } else { + timespec = Qt::UTC; } - result += (year - 1968) / 4; - result -= (year - 1900) / 100; - result += (year - 1600) / 400; - result += time->tm_mday; - result -= 1; - result *= 24; - result += time->tm_hour; - result *= 60; - result += time->tm_min; - result *= 60; - result += time->tm_sec; - return (result); -} -/* - * mklocaltime: same as mktime, but try to recover the "Summer time flag", - * which is evaluated by mktime - */ -time_t -mklocaltime(std::tm* time) -{ - time_t result; - std::tm check = *time; - - check.tm_isdst = 0; - result = mktime(&check); - check = *localtime(&result); - if (check.tm_isdst == 1) { /* DST is in effect */ - check = *time; - check.tm_isdst = 1; - result = mktime(&check); + if (date.isValid() && time.isValid()) { + result = QDateTime(date, time, timespec, offset); + } else if (time.isValid()) { + // TODO: Wouldn't it be better to return an invalid QDateTime + // that contained an invalid QDate, a valid QTime and a valid + // Qt::TimeSpec? + result = QDateTime(QDate(1970, 1, 1), time, timespec, offset); + } else if (date.isValid()) { + // no time, use start of day in the given Qt::TimeSpec. +#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) + result = QDateTime(date, QTime(0,0), timespec, offset); +#else + result = date.startOfDay(timespec, offset); +#endif } + return result; } @@ -548,6 +525,13 @@ QDateTime dotnet_time_to_qdatetime(long long dotnet) return epoch.addMSecs(millisecs); } +long long qdatetime_to_dotnet_time(const QDateTime& dt) +{ + QDateTime epoch = QDateTime(QDate(1, 1, 1), QTime(0, 0, 0), Qt::UTC); + qint64 millisecs = epoch.msecsTo(dt); + return millisecs * 10000; +} + double endian_read_double(const void* ptr, int read_le) { diff --git a/xcsv.cc b/xcsv.cc index 2d42b6f6b..fa7e6dbdc 100644 --- a/xcsv.cc +++ b/xcsv.cc @@ -28,6 +28,7 @@ #include // for isdigit, tolower #include // for fabs, pow #include // for snprintf, sscanf +#include // for uint32_t #include // for strtod #include // for strlen, strncmp, strcmp #include // for gmtime, localtime, time_t, mktime, strftime @@ -96,8 +97,6 @@ const QHash XcsvStyle::xcsv_tokens { { "GPS_SAT", XT_GPS_SAT }, { "GPS_VDOP", XT_GPS_VDOP }, { "HEART_RATE", XT_HEART_RATE }, - { "HMSG_TIME", XT_HMSG_TIME }, - { "HMSL_TIME", XT_HMSL_TIME }, { "ICON_DESCR", XT_ICON_DESCR }, { "IGNORE", XT_IGNORE }, { "INDEX", XT_INDEX }, @@ -245,67 +244,82 @@ XcsvStyle::xcsv_ofield_add(XcsvStyle* style, const QString& qkey, const QString& style->ofields.append(fmp); } -QDateTime +QDate XcsvFormat::yyyymmdd_to_time(const QString& s) { - QDate d = QDate::fromString(s, "yyyyMMdd"); -#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) - return QDateTime(d); -#else - return d.startOfDay(); -#endif + return QDate::fromString(s, "yyyyMMdd"); } +QDateTime +XcsvFormat::xcsv_adjust_time(const QDate date, const QTime time, bool is_localtime) const +{ + return make_datetime(date, time, is_localtime, opt_utc != nullptr, utc_offset); +} /* * sscanftime - Parse a date buffer using strftime format */ -time_t -XcsvFormat::sscanftime(const char* s, const char* format, bool gmt) +void +XcsvFormat::sscanftime(const char* s, const char* format, QDate& date, QTime& time) { std::tm stm{}; + stm.tm_sec = -1; + stm.tm_min = -1; + stm.tm_hour = -1; + stm.tm_mday = -1; + stm.tm_mon = -1; + stm.tm_year = -1; + stm.tm_wday = -1; + stm.tm_yday = -1; + stm.tm_isdst = -1; if (strptime(s, format, &stm)) { - if ((stm.tm_mday == 0) && (stm.tm_mon == 0) && (stm.tm_year == 0)) { - stm.tm_mday = 1; - stm.tm_mon = 0; - stm.tm_year = 70; - } - stm.tm_isdst = -1; - if (gmt) { - return mkgmtime(&stm); - } else { - return mktime(&stm); + + std::optional time_result; + bool bad_time_parse = false; + if (stm.tm_hour >= 0 && stm.tm_min >= 0 && stm.tm_sec >= 0) { + time_result = QTime(stm.tm_hour, stm.tm_min, stm.tm_sec); + } else if (stm.tm_hour >= 0 && stm.tm_min >= 0) { + time_result = QTime(stm.tm_hour, stm.tm_min, 0); + } else if (stm.tm_hour >= 0) { + time_result = QTime(stm.tm_hour, 0, 0); + } else if (!(stm.tm_hour == -1 && stm.tm_min == -1 && stm.tm_sec == -1)) { + bad_time_parse = true; } - } - // Don't fuss for empty strings. - if (*s) { - warning("date parse of string '%s' with format '%s' failed.\n", + if ((time_result.has_value() && !time_result->isValid()) || bad_time_parse) { + fatal(MYNAME ": couldn't parse time from string '%s' with format '%s'.\n", s, format); - } - return 0; -} + } + if (time_result.has_value()) { + time = *time_result; + } -time_t -XcsvFormat::addhms(const char* s, const char* format) -{ - time_t tt = 0; - int hour = 0; - int min = 0; - int sec = 0; - - char* ampm = (char*) xmalloc(strlen(s) + 1); - int ac = sscanf(s, format, &hour, &min, &sec, ampm); - /* If no time format in arg string, assume AM */ - if (ac < 4) { - ampm[0] = 0; - } - if (ac) { - tt = ((tolower(ampm[0])=='p') ? 43200 : 0) + 3600 * hour + 60 * min + sec; + std::optional date_result; + bool bad_date_parse = false; + int year_result = (stm.tm_year >= 70)? stm.tm_year + 1900 : stm.tm_year + 2000; + if (stm.tm_year >= 0 && stm.tm_mon >= 0 && stm.tm_mday >= 0) { + date_result = QDate(year_result, stm.tm_mon + 1, stm.tm_mday); + } else if (stm.tm_year >= 0 && stm.tm_mon >= 0) { + date_result = QDate(year_result, stm.tm_mon + 1, 1); + } else if (stm.tm_year >= 0) { + date_result = QDate(year_result, 1, 1); + } else if (!(stm.tm_year == -1 && stm.tm_mon == -1 && stm.tm_mday == -1)) { + bad_date_parse = true; + } + if ((date_result.has_value() && !date_result->isValid()) || bad_date_parse) { + fatal(MYNAME ": couldn't parse date from string '%s' with format '%s'.\n", + s, format); + } + if (date_result.has_value()) { + date = *date_result; + } + } else { + // Don't fuss for empty strings. + if (*s) { + warning("date parse of string '%s' with format '%s' failed.\n", + s, format); + } } - xfree(ampm); - - return tt; } QString @@ -331,40 +345,14 @@ XcsvFormat::writetime(const char* format, time_t t, bool gmt) QString XcsvFormat::writetime(const char* format, const gpsbabel::DateTime& t, bool gmt) { - return writetime(format, t.toTime_t(), gmt); -} - -QString -XcsvFormat::writehms(const char* format, time_t t, bool gmt) -{ - static const std::tm no_time{}; - static const std::tm* stmp = &no_time; - - if (gmt) { - stmp = gmtime(&t); - } else { - stmp = localtime(&t); - } - - if (stmp == nullptr) { - stmp = &no_time; - } - - return QString::asprintf(format, - stmp->tm_hour, stmp->tm_min, stmp->tm_sec, - (stmp->tm_hour >= 12 ? "PM" : "AM")); -} - -QString -XcsvFormat::writehms(const char* format, const gpsbabel::DateTime& t, bool gmt) -{ - return writehms(format, t.toTime_t(), gmt); + uint32_t tt = t.toTime_t(); + return (tt == 0xffffffffU)? QString() : writetime(format, tt, gmt); } long XcsvFormat::time_to_yyyymmdd(const QDateTime& t) { - QDate d = t.date(); + QDate d = t.toUTC().date(); return d.year() * 10000 + d.month() * 100 + d.day(); } @@ -594,15 +582,26 @@ XcsvFormat::xcsv_parse_val(const QString& value, Waypoint* wpt, const XcsvStyle: break; /* TIME CONVERSIONS ***************************************************/ - case XcsvStyle::XT_EXCEL_TIME: + case XcsvStyle::XT_EXCEL_TIME: { /* Time as Excel Time */ - wpt->SetCreationTime(excel_to_timet(strtod(s, nullptr))); - break; + bool ok; + double et = value.toDouble(&ok); + if (ok) { + wpt->SetCreationTime(0, excel_to_timetms(et)); + parse_data->need_datetime = false; + } else if (!value.isEmpty()) { + warning("parse of string '%s' on line number %d as EXCEL_TIME failed.\n", s, line_no); + } + } + break; case XcsvStyle::XT_TIMET_TIME: { /* Time as time_t */ bool ok; - wpt->SetCreationTime(value.toLongLong(&ok)); - if (!ok) { + long long tt = value.toLongLong(&ok); + if (ok) { + wpt->SetCreationTime(tt); + parse_data->need_datetime = false; + } else if (!value.isEmpty()) { warning("parse of string '%s' on line number %d as TIMET_TIME failed.\n", s, line_no); } } @@ -610,47 +609,50 @@ XcsvFormat::xcsv_parse_val(const QString& value, Waypoint* wpt, const XcsvStyle: case XcsvStyle::XT_TIMET_TIME_MS: { /* Time as time_t in milliseconds */ bool ok; - wpt->SetCreationTime(0, value.toLongLong(&ok)); - if (!ok) { + long long tt = value.toLongLong(&ok); + if (ok) { + wpt->SetCreationTime(0, tt); + parse_data->need_datetime = false; + } else if (!value.isEmpty()) { warning("parse of string '%s' on line number %d as TIMET_TIME_MS failed.\n", s, line_no); } } break; case XcsvStyle::XT_YYYYMMDD_TIME: - wpt->SetCreationTime(yyyymmdd_to_time(value)); + parse_data->utc_date = yyyymmdd_to_time(value); break; case XcsvStyle::XT_GMT_TIME: - wpt->SetCreationTime(sscanftime(s, fmp.printfc.constData(), true)); + sscanftime(s, fmp.printfc.constData(), parse_data->utc_date, parse_data->utc_time); break; case XcsvStyle::XT_LOCAL_TIME: - if (!gpsbabel_testmode()) { - wpt->creation_time = wpt->creation_time.addSecs(sscanftime(s, fmp.printfc.constData(), false)); - } else { - /* Force constant time zone for test */ - wpt->creation_time = wpt->creation_time.addSecs(sscanftime(s, fmp.printfc.constData(), true)); - } - break; - /* Useful when time and date are in separate fields - GMT / Local offset is handled by the two cases above */ - case XcsvStyle::XT_HMSG_TIME: - case XcsvStyle::XT_HMSL_TIME: - wpt->creation_time = wpt->creation_time.addSecs(addhms(s, fmp.printfc.constData())); + sscanftime(s, fmp.printfc.constData(), parse_data->local_date, parse_data->local_time); break; case XcsvStyle::XT_ISO_TIME: case XcsvStyle::XT_ISO_TIME_MS: - wpt->SetCreationTime(xml_parse_time(value)); + wpt->SetCreationTime(QDateTime::fromString(value, Qt::ISODateWithMs)); + parse_data->need_datetime = false; break; case XcsvStyle::XT_NET_TIME: { bool ok; - wpt->SetCreationTime(dotnet_time_to_qdatetime(value.toLongLong(&ok))); - if (!ok) { + long long dnt = value.toLongLong(&ok); + if (ok) { + wpt->SetCreationTime(dotnet_time_to_qdatetime(dnt)); + parse_data->need_datetime = false; + } else if (!value.isEmpty()) { warning("parse of string '%s' on line number %d as NET_TIME failed.\n", s, line_no); } } break; - case XcsvStyle::XT_GEOCACHE_LAST_FOUND: - wpt->AllocGCData()->last_found = yyyymmdd_to_time(value); + case XcsvStyle::XT_GEOCACHE_LAST_FOUND: { + QDate date; + date = yyyymmdd_to_time(value); +#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) + wpt->AllocGCData()->last_found = QDateTime(date); +#else + wpt->AllocGCData()->last_found = date.startOfDay(); +#endif break; + } /* GEOCACHING STUFF ***************************************************/ case XcsvStyle::XT_GEOCACHE_DIFF: @@ -882,6 +884,23 @@ XcsvFormat::read() } } + + if (parse_data.need_datetime) { + if (parse_data.utc_date.isValid() && parse_data.utc_time.isValid()) { + wpt_tmp->SetCreationTime(xcsv_adjust_time(parse_data.utc_date, parse_data.utc_time, false)); + } else if (parse_data.local_date.isValid() && parse_data.local_time.isValid()) { + wpt_tmp->SetCreationTime(xcsv_adjust_time(parse_data.local_date, parse_data.local_time, true)); + } else if (parse_data.utc_date.isValid()) { + wpt_tmp->SetCreationTime(xcsv_adjust_time(parse_data.utc_date, parse_data.utc_time, false)); + } else if (parse_data.local_date.isValid()) { + wpt_tmp->SetCreationTime(xcsv_adjust_time(parse_data.local_date, parse_data.local_time, true)); + } else if (parse_data.utc_time.isValid()) { + wpt_tmp->SetCreationTime(xcsv_adjust_time(parse_data.utc_date, parse_data.utc_time, false)); + } else if (parse_data.local_time.isValid()) { + wpt_tmp->SetCreationTime(xcsv_adjust_time(parse_data.local_date, parse_data.local_time, true)); + } + } + // If XT_LAT_DIR(XT_LON_DIR) was an input field, and the latitude(longitude) is positive, // assume the latitude(longitude) was the absolute value and take the sign from XT_LAT_DIR(XT_LON_DIR). if (parse_data.lat_dir_positive.has_value() && !(*parse_data.lat_dir_positive) && (wpt_tmp->latitude > 0.0)) { @@ -1361,18 +1380,26 @@ XcsvFormat::xcsv_waypt_pr(const Waypoint* wpt) /* TIME CONVERSIONS**************************************************/ case XcsvStyle::XT_EXCEL_TIME: /* creation time as an excel (double) time */ - buff = QString::asprintf(fmp.printfc.constData(), timet_to_excel(wpt->GetCreationTime().toTime_t())); + if (wpt->GetCreationTime().isValid()) { + buff = QString::asprintf(fmp.printfc.constData(), timetms_to_excel(wpt->GetCreationTime().toMSecsSinceEpoch())); + } break; case XcsvStyle::XT_TIMET_TIME: /* time as a time_t variable in seconds */ - buff = QString::asprintf(fmp.printfc.constData(), wpt->GetCreationTime().toSecsSinceEpoch()); + if (wpt->GetCreationTime().isValid()) { + buff = QString::asprintf(fmp.printfc.constData(), wpt->GetCreationTime().toSecsSinceEpoch()); + } break; case XcsvStyle::XT_TIMET_TIME_MS: /* time as a time_t variable in milliseconds */ - buff = QString::asprintf(fmp.printfc.constData(), wpt->GetCreationTime().toMSecsSinceEpoch()); + if (wpt->GetCreationTime().isValid()) { + buff = QString::asprintf(fmp.printfc.constData(), wpt->GetCreationTime().toMSecsSinceEpoch()); + } break; case XcsvStyle::XT_YYYYMMDD_TIME: - buff = QString::asprintf(fmp.printfc.constData(), time_to_yyyymmdd(wpt->GetCreationTime())); + if (wpt->GetCreationTime().isValid()) { + buff = QString::asprintf(fmp.printfc.constData(), time_to_yyyymmdd(wpt->GetCreationTime())); + } break; case XcsvStyle::XT_GMT_TIME: buff = writetime(fmp.printfc.constData(), wpt->GetCreationTime(), true); @@ -1380,17 +1407,20 @@ XcsvFormat::xcsv_waypt_pr(const Waypoint* wpt) case XcsvStyle::XT_LOCAL_TIME: buff = writetime(fmp.printfc.constData(), wpt->GetCreationTime(), false); break; - case XcsvStyle::XT_HMSG_TIME: - buff = writehms(fmp.printfc.constData(), wpt->GetCreationTime(), true); - break; - case XcsvStyle::XT_HMSL_TIME: - buff = writehms(fmp.printfc.constData(), wpt->GetCreationTime(), false); - break; case XcsvStyle::XT_ISO_TIME: - buff = writetime("%Y-%m-%dT%H:%M:%SZ", wpt->GetCreationTime(), true); + if (wpt->GetCreationTime().isValid()) { + buff = wpt->GetCreationTime().toUTC().toString(Qt::ISODate); + } break; case XcsvStyle::XT_ISO_TIME_MS: - buff = wpt->GetCreationTime().toPrettyString(); + if (wpt->GetCreationTime().isValid()) { + buff = wpt->GetCreationTime().toPrettyString(); + } + break; + case XcsvStyle::XT_NET_TIME: + if (wpt->GetCreationTime().isValid()) { + buff = QString::number(qdatetime_to_dotnet_time(wpt->GetCreationTime())); + } break; case XcsvStyle::XT_GEOCACHE_LAST_FOUND: buff = QString::asprintf(fmp.printfc.constData(), time_to_yyyymmdd(wpt->gc_data->last_found)); @@ -1883,6 +1913,8 @@ XcsvFormat::rd_init(const QString& fname) if (xcsv_file->gps_datum_idx < 0) { fatal(MYNAME ": datum \"%s\" is not supported.", qPrintable(datum_name)); } + + utc_offset = (opt_utc == nullptr)? 0 : xstrtoi(opt_utc, nullptr, 10) * SECONDS_PER_HOUR; } void diff --git a/xcsv.h b/xcsv.h index 97d581f79..0cbfd1345 100644 --- a/xcsv.h +++ b/xcsv.h @@ -26,12 +26,15 @@ #include // for move #include // for QByteArray +#include // for QDate #include // for QDateTime #include // for QHash #include // for QList #include // for QString #include // for QStringList +#include // for QTime #include // for QVector +#include // for qRound64 #include "defs.h" #include "format.h" @@ -86,8 +89,6 @@ public: XT_GPS_SAT, XT_GPS_VDOP, XT_HEART_RATE, - XT_HMSG_TIME, - XT_HMSL_TIME, XT_ICON_DESCR, XT_IGNORE, XT_INDEX, @@ -332,6 +333,11 @@ private: UrlLink* link_{nullptr}; std::optional lat_dir_positive; std::optional lon_dir_positive; + QDate local_date; + QTime local_time; + QDate utc_date; + QTime utc_time; + bool need_datetime{true}; }; /* Constants */ @@ -346,20 +352,21 @@ private: } /* convert excel time (days since 1900) to time_t and back again */ - static constexpr double excel_to_timet(double a) + static constexpr qint64 excel_to_timetms(double a) { - return (a - 25569.0) * 86400.0; + return qRound64((a - 25569.0) * 86400000.0); } - static constexpr double timet_to_excel(double a) + static constexpr double timetms_to_excel(qint64 a) { - return (a / 86400.0) + 25569.0; + return (a / 86400000.0) + 25569.0; } /* Member Functions */ - static QDateTime yyyymmdd_to_time(const QString& s); - static time_t sscanftime(const char* s, const char* format, bool gmt); - static time_t addhms(const char* s, const char* format); + static QDate yyyymmdd_to_time(const QString& s); + QDateTime xcsv_adjust_time(const QDate date, const QTime time, bool is_localtime) const; + static void sscanftime(const char* s, const char* format, QDate& date, QTime& time); + static QTime addhms(const char* s, const char* format); static QString writetime(const char* format, time_t t, bool gmt); static QString writetime(const char* format, const gpsbabel::DateTime& t, bool gmt); static QString writehms(const char* format, time_t t, bool gmt); @@ -391,6 +398,8 @@ private: char* prefer_shortnames = nullptr; char* xcsv_urlbase = nullptr; char* opt_datum = nullptr; + char* opt_utc = nullptr; + int utc_offset{}; QString intstylefile; @@ -428,6 +437,10 @@ private: "datum", &opt_datum, "GPS datum (def. WGS 84)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr }, + { + "utc", &opt_utc, "Write timestamps with offset x to UTC time", + nullptr, ARGTYPE_INT, "-14", "+14", nullptr + }, }; }; diff --git a/xmldoc/chapters/styles.xml b/xmldoc/chapters/styles.xml index f83e6f01b..8c3efbcb4 100644 --- a/xmldoc/chapters/styles.xml +++ b/xmldoc/chapters/styles.xml @@ -829,7 +829,7 @@ longitude)
YYYYMMDD_TIME - YYYYMMDD_TIME is the waypoint's creation time, if any. It's a single + YYYYMMDD_TIME is the waypoint's creation time in UTC, if any. It's a single decimal field containing four digits of year, two digits of month, and two digits of date. Internally it is a LONG INTEGER and thus requires a LONG INTEGER printf conversion. @@ -843,54 +843,30 @@ longitude) GMT_TIME GMT_TIME is the waypoint's creation time, in UTC time zone. It uses the strptime conversion format tags. + It can be used to parse/print a date and time in one column, + or a date in one column and a time in another column. If used to parse a date and a + time in separate columns the date and time can be in either order. - example: - - IFIELD GMT_TIME,"","%m/%d/%Y %I:%M:%D %p" +example with a a date followed by a time using a 12 hour clock and an AM/PM indication: +IFIELD GMT_TIME,"","%m/%d/%Y %I:%M:%S %p" + +example with a a date followed by a time using a 24 hour clock: +IFIELD GMT_TIME,"","%Y/%m/%d" +IFIELD GMT_TIME,"","%H:%M:%S" - Search the web for 'strptime man page' for details strptime, but one +Search the web for 'strptime man page' for details strptime, but one such page can be found at - - - - - - - - http://www.die.net/doc/linux/man/man3/strptime.3.html + http://www.die.net/doc/linux/man/man3/strptime.3.html
LOCAL_TIME LOCAL_TIME is the waypoint's creation time, in the local - time zone. It uses strptime conversion format tags. See GMT_TIME for a - reference. - - example: + time zone. It uses strptime conversion format tags. + It can be used to parse/print a date and time in one column, + or a date in one column and a time in another column. If used to parse a date and a + time in separate columns the date and time can be in either order. + See GMT_TIME for examples and a reference. - IFIELD LOCAL_TIME,"","%y-%m-%d" - -
-
- HMSG_TIME - HMSG_TIME parses up to three time parts and am/pm string to add - this value to the previously parsed *_TIME field that contains - only a date. On output, will print the time in UTC. - - example: - - IFIELD HMSG_TIME,"","%02d:%02d:%02d %s" - -
-
- HMSL_TIME - HMSG_TIME parses up to three time parts and am/pm string to add - this value to the previously parsed *_TIME field that contains - only a date. On output, will print the time in local time. - - example: - - IFIELD HMSL_TIME,"","%dh%dm" -
ISO_TIME diff --git a/xmldoc/formats/options/unicsv-utc.xml b/xmldoc/formats/options/unicsv-utc.xml index 2c4d062ce..36652438f 100644 --- a/xmldoc/formats/options/unicsv-utc.xml +++ b/xmldoc/formats/options/unicsv-utc.xml @@ -1,5 +1,6 @@ -This option specifies the local time zone to use when writing times. It -is specified as an offset from Universal Coordinated Time (UTC) in hours. -Valid values are from -23 to +23. +This option specifies the local time zone to use when reading and writing times. It +specifies a Universal Coordinated Time (UTC) offset in hours. For example, in the +winter in Sweden the UTC offset is UTC+1, which corresponds to an option utc=1. +Valid values are from -14 to +14. -- 2.30.2